chiark / gitweb /
Commit 2.4.5-5 as unpacked orig.unpacked
authorIan Jackson <ian@liberator.(none)>
Wed, 25 Nov 2009 18:15:09 +0000 (18:15 +0000)
committerIan Jackson <ian@liberator.(none)>
Wed, 25 Nov 2009 18:15:09 +0000 (18:15 +0000)
748 files changed:
CONTRIBUTORS [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
HACKING [new file with mode: 0644]
INSTALL [new file with mode: 0644]
LICENSE [new file with mode: 0644]
MANIFEST [new file with mode: 0644]
Makefile [new file with mode: 0644]
Makefile.global.in [new file with mode: 0644]
NEWS [new file with mode: 0644]
README [new file with mode: 0644]
TODO [new file with mode: 0644]
aclocal.m4 [new file with mode: 0644]
authprogs/Makefile [new file with mode: 0644]
authprogs/auth_krb5.c [new file with mode: 0644]
authprogs/auth_smb.c [new file with mode: 0644]
authprogs/ckpasswd.c [new file with mode: 0644]
authprogs/domain.c [new file with mode: 0644]
authprogs/ident.c [new file with mode: 0644]
authprogs/libauth.c [new file with mode: 0644]
authprogs/libauth.h [new file with mode: 0644]
authprogs/radius.c [new file with mode: 0644]
authprogs/smbval/Makefile [new file with mode: 0644]
authprogs/smbval/byteorder.h [new file with mode: 0644]
authprogs/smbval/rfcnb-common.h [new file with mode: 0644]
authprogs/smbval/rfcnb-error.h [new file with mode: 0644]
authprogs/smbval/rfcnb-io.c [new file with mode: 0644]
authprogs/smbval/rfcnb-io.h [new file with mode: 0644]
authprogs/smbval/rfcnb-priv.h [new file with mode: 0644]
authprogs/smbval/rfcnb-util.c [new file with mode: 0644]
authprogs/smbval/rfcnb-util.h [new file with mode: 0644]
authprogs/smbval/rfcnb.h [new file with mode: 0644]
authprogs/smbval/session.c [new file with mode: 0644]
authprogs/smbval/smbdes.c [new file with mode: 0644]
authprogs/smbval/smbencrypt.c [new file with mode: 0644]
authprogs/smbval/smblib-common.h [new file with mode: 0644]
authprogs/smbval/smblib-priv.h [new file with mode: 0644]
authprogs/smbval/smblib-util.c [new file with mode: 0644]
authprogs/smbval/smblib.c [new file with mode: 0644]
authprogs/smbval/smblib.h [new file with mode: 0644]
authprogs/smbval/valid.c [new file with mode: 0644]
authprogs/smbval/valid.h [new file with mode: 0644]
backends/Makefile [new file with mode: 0644]
backends/actmerge.in [new file with mode: 0644]
backends/actsync.c [new file with mode: 0644]
backends/actsyncd.in [new file with mode: 0644]
backends/archive.c [new file with mode: 0644]
backends/batcher.c [new file with mode: 0644]
backends/buffchan.c [new file with mode: 0644]
backends/crosspost.c [new file with mode: 0644]
backends/cvtbatch.c [new file with mode: 0644]
backends/filechan.c [new file with mode: 0644]
backends/inndf.c [new file with mode: 0644]
backends/innxbatch.c [new file with mode: 0644]
backends/innxmit.c [new file with mode: 0644]
backends/map.c [new file with mode: 0644]
backends/map.h [new file with mode: 0644]
backends/mod-active.in [new file with mode: 0644]
backends/news2mail.in [new file with mode: 0644]
backends/ninpaths.c [new file with mode: 0644]
backends/nntpget.c [new file with mode: 0644]
backends/nntpsend.in [new file with mode: 0644]
backends/overchan.c [new file with mode: 0644]
backends/send-ihave.in [new file with mode: 0644]
backends/send-nntp.in [new file with mode: 0644]
backends/send-uucp.in [new file with mode: 0644]
backends/sendinpaths.in [new file with mode: 0644]
backends/sendxbatches.in [new file with mode: 0644]
backends/shlock.c [new file with mode: 0644]
backends/shrinkfile.c [new file with mode: 0644]
configure [new file with mode: 0755]
configure.in [new file with mode: 0644]
contrib/Makefile [new file with mode: 0644]
contrib/README [new file with mode: 0644]
contrib/archivegz.in [new file with mode: 0644]
contrib/auth_pass.README [new file with mode: 0644]
contrib/auth_pass.c [new file with mode: 0644]
contrib/backlogstat.in [new file with mode: 0644]
contrib/backupfeed.in [new file with mode: 0644]
contrib/cleannewsgroups.in [new file with mode: 0644]
contrib/count_overview.pl [new file with mode: 0755]
contrib/delayer.in [new file with mode: 0644]
contrib/expirectl.c [new file with mode: 0644]
contrib/findreadgroups.in [new file with mode: 0644]
contrib/fixhist [new file with mode: 0755]
contrib/innconfcheck [new file with mode: 0755]
contrib/makeexpctl.in [new file with mode: 0644]
contrib/makestorconf.in [new file with mode: 0644]
contrib/mkbuf [new file with mode: 0755]
contrib/mlockfile.c [new file with mode: 0644]
contrib/newsresp.c [new file with mode: 0644]
contrib/pullart.c [new file with mode: 0644]
contrib/reset-cnfs.c [new file with mode: 0644]
contrib/respool.c [new file with mode: 0644]
contrib/sample.init.script [new file with mode: 0644]
contrib/showtoken.in [new file with mode: 0644]
contrib/stathist.in [new file with mode: 0644]
contrib/thdexpire.in [new file with mode: 0644]
contrib/tunefeed.in [new file with mode: 0644]
control/Makefile [new file with mode: 0644]
control/controlbatch.in [new file with mode: 0644]
control/controlchan.in [new file with mode: 0644]
control/docheckgroups.in [new file with mode: 0644]
control/gpgverify.in [new file with mode: 0644]
control/modules/checkgroups.pl [new file with mode: 0644]
control/modules/ihave.pl [new file with mode: 0644]
control/modules/newgroup.pl [new file with mode: 0644]
control/modules/rmgroup.pl [new file with mode: 0644]
control/modules/sendme.pl [new file with mode: 0644]
control/modules/sendsys.pl [new file with mode: 0644]
control/modules/senduuname.pl [new file with mode: 0644]
control/modules/version.pl [new file with mode: 0644]
control/perl-nocem.in [new file with mode: 0644]
control/pgpverify.in [new file with mode: 0644]
control/signcontrol.in [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/changelog.old [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/inn2-dev.files [new file with mode: 0644]
debian/inn2-dev.links [new file with mode: 0644]
debian/inn2-inews.files [new file with mode: 0644]
debian/inn2-inews.links [new file with mode: 0644]
debian/inn2.README.Debian [new file with mode: 0644]
debian/inn2.cron.d [new file with mode: 0644]
debian/inn2.docs [new file with mode: 0644]
debian/inn2.examples [new file with mode: 0644]
debian/inn2.init [new file with mode: 0644]
debian/inn2.links [new file with mode: 0644]
debian/inn2.logcheck.ignore.server [new file with mode: 0644]
debian/inn2.logcheck.violations.ignore [new file with mode: 0644]
debian/inn2.postinst [new file with mode: 0644]
debian/inn2.postrm [new file with mode: 0644]
debian/inn2.preinst [new file with mode: 0644]
debian/inn2.prerm [new file with mode: 0644]
debian/patches/configure-hostname [new file with mode: 0644]
debian/patches/debian-paths [new file with mode: 0644]
debian/patches/fix_ad_flag [new file with mode: 0644]
debian/patches/fix_body_regexps [new file with mode: 0644]
debian/patches/no-makedbz-on-install [new file with mode: 0644]
debian/patches/nocem-gpg-import [new file with mode: 0644]
debian/patches/series [new file with mode: 0644]
debian/patches/typo_inn_conf_man [new file with mode: 0644]
debian/patches/u_innreport_misc [new file with mode: 0644]
debian/patches/u_right_length [new file with mode: 0644]
debian/patches/u_status_init_ip [new file with mode: 0644]
debian/patches/u_tls_duplicate_reply [new file with mode: 0644]
debian/patches/u_xhdr_permissions [new file with mode: 0644]
debian/patches/u_xover_duplicate_reply [new file with mode: 0644]
debian/rules [new file with mode: 0755]
debian/watch [new file with mode: 0644]
doc/GPL [new file with mode: 0644]
doc/IPv6-info [new file with mode: 0644]
doc/Makefile [new file with mode: 0644]
doc/checklist [new file with mode: 0644]
doc/compliance-nntp [new file with mode: 0644]
doc/config-design [new file with mode: 0644]
doc/config-semantics [new file with mode: 0644]
doc/config-syntax [new file with mode: 0644]
doc/external-auth [new file with mode: 0644]
doc/history [new file with mode: 0644]
doc/hook-perl [new file with mode: 0644]
doc/hook-python [new file with mode: 0644]
doc/hook-tcl [new file with mode: 0644]
doc/man/Makefile [new file with mode: 0644]
doc/man/active.5 [new file with mode: 0644]
doc/man/active.times.5 [new file with mode: 0644]
doc/man/actsync.8 [new file with mode: 0644]
doc/man/actsyncd.8 [new file with mode: 0644]
doc/man/archive.8 [new file with mode: 0644]
doc/man/auth_krb5.8 [new file with mode: 0644]
doc/man/auth_smb.8 [new file with mode: 0644]
doc/man/batcher.8 [new file with mode: 0644]
doc/man/buffchan.8 [new file with mode: 0644]
doc/man/buffindexed.conf.5 [new file with mode: 0644]
doc/man/ckpasswd.8 [new file with mode: 0644]
doc/man/clientlib.3 [new file with mode: 0644]
doc/man/cnfsheadconf.8 [new file with mode: 0644]
doc/man/cnfsstat.8 [new file with mode: 0644]
doc/man/control.ctl.5 [new file with mode: 0644]
doc/man/controlchan.8 [new file with mode: 0644]
doc/man/convdate.1 [new file with mode: 0644]
doc/man/ctlinnd.8 [new file with mode: 0644]
doc/man/cvtbatch.8 [new file with mode: 0644]
doc/man/cycbuff.conf.5 [new file with mode: 0644]
doc/man/dbz.3 [new file with mode: 0644]
doc/man/distrib.pats.5 [new file with mode: 0644]
doc/man/domain.8 [new file with mode: 0644]
doc/man/expire.8 [new file with mode: 0644]
doc/man/expire.ctl.5 [new file with mode: 0644]
doc/man/expireover.8 [new file with mode: 0644]
doc/man/expirerm.8 [new file with mode: 0644]
doc/man/fastrm.1 [new file with mode: 0644]
doc/man/filechan.8 [new file with mode: 0644]
doc/man/getlist.1 [new file with mode: 0644]
doc/man/grephistory.1 [new file with mode: 0644]
doc/man/history.5 [new file with mode: 0644]
doc/man/ident.8 [new file with mode: 0644]
doc/man/incoming.conf.5 [new file with mode: 0644]
doc/man/inews.1 [new file with mode: 0644]
doc/man/inn.conf.5 [new file with mode: 0644]
doc/man/inncheck.8 [new file with mode: 0644]
doc/man/innconfval.1 [new file with mode: 0644]
doc/man/innd.8 [new file with mode: 0644]
doc/man/inndcomm.3 [new file with mode: 0644]
doc/man/inndf.8 [new file with mode: 0644]
doc/man/inndstart.8 [new file with mode: 0644]
doc/man/innfeed.1 [new file with mode: 0644]
doc/man/innfeed.conf.5 [new file with mode: 0644]
doc/man/innmail.1 [new file with mode: 0644]
doc/man/innreport.8 [new file with mode: 0644]
doc/man/innstat.8 [new file with mode: 0644]
doc/man/innupgrade.8 [new file with mode: 0644]
doc/man/innwatch.8 [new file with mode: 0644]
doc/man/innwatch.ctl.5 [new file with mode: 0644]
doc/man/innxbatch.8 [new file with mode: 0644]
doc/man/innxmit.8 [new file with mode: 0644]
doc/man/libauth.3 [new file with mode: 0644]
doc/man/libinn.3 [new file with mode: 0644]
doc/man/libinnhist.3 [new file with mode: 0644]
doc/man/libstorage.3 [new file with mode: 0644]
doc/man/list.3 [new file with mode: 0644]
doc/man/mailpost.8 [new file with mode: 0644]
doc/man/makeactive.8 [new file with mode: 0644]
doc/man/makedbz.8 [new file with mode: 0644]
doc/man/makehistory.8 [new file with mode: 0644]
doc/man/mod-active.8 [new file with mode: 0644]
doc/man/moderators.5 [new file with mode: 0644]
doc/man/motd.news.5 [new file with mode: 0644]
doc/man/news.daily.8 [new file with mode: 0644]
doc/man/news2mail.8 [new file with mode: 0644]
doc/man/newsfeeds.5 [new file with mode: 0644]
doc/man/newslog.5 [new file with mode: 0644]
doc/man/ninpaths.8 [new file with mode: 0644]
doc/man/nnrpd.8 [new file with mode: 0644]
doc/man/nnrpd.track.5 [new file with mode: 0644]
doc/man/nntpget.1 [new file with mode: 0644]
doc/man/nntpsend.8 [new file with mode: 0644]
doc/man/nntpsend.ctl.5 [new file with mode: 0644]
doc/man/ovdb.5 [new file with mode: 0644]
doc/man/ovdb_init.8 [new file with mode: 0644]
doc/man/ovdb_monitor.8 [new file with mode: 0644]
doc/man/ovdb_server.8 [new file with mode: 0644]
doc/man/ovdb_stat.8 [new file with mode: 0644]
doc/man/overchan.8 [new file with mode: 0644]
doc/man/overview.fmt.5 [new file with mode: 0644]
doc/man/parsedate.3 [new file with mode: 0644]
doc/man/passwd.nntp.5 [new file with mode: 0644]
doc/man/perl-nocem.8 [new file with mode: 0644]
doc/man/pgpverify.1 [new file with mode: 0644]
doc/man/prunehistory.8 [new file with mode: 0644]
doc/man/pullnews.1 [new file with mode: 0644]
doc/man/putman.sh [new file with mode: 0644]
doc/man/qio.3 [new file with mode: 0644]
doc/man/radius.8 [new file with mode: 0644]
doc/man/radius.conf.5 [new file with mode: 0644]
doc/man/rc.news.8 [new file with mode: 0644]
doc/man/readers.conf.5 [new file with mode: 0644]
doc/man/rnews.1 [new file with mode: 0644]
doc/man/sasl.conf.5 [new file with mode: 0644]
doc/man/scanlogs.8 [new file with mode: 0644]
doc/man/send-nntp.8 [new file with mode: 0644]
doc/man/send-uucp.8 [new file with mode: 0644]
doc/man/sendinpaths.8 [new file with mode: 0644]
doc/man/shlock.1 [new file with mode: 0644]
doc/man/shrinkfile.1 [new file with mode: 0644]
doc/man/simpleftp.1 [new file with mode: 0644]
doc/man/sm.1 [new file with mode: 0644]
doc/man/startinnfeed.1 [new file with mode: 0644]
doc/man/storage.conf.5 [new file with mode: 0644]
doc/man/subscriptions.5 [new file with mode: 0644]
doc/man/tally.control.8 [new file with mode: 0644]
doc/man/tdx-util.8 [new file with mode: 0644]
doc/man/tst.3 [new file with mode: 0644]
doc/man/uwildmat.3 [new file with mode: 0644]
doc/man/writelog.8 [new file with mode: 0644]
doc/pod/Makefile [new file with mode: 0644]
doc/pod/active.pod [new file with mode: 0644]
doc/pod/active.times.pod [new file with mode: 0644]
doc/pod/auth_krb5.pod [new file with mode: 0644]
doc/pod/auth_smb.pod [new file with mode: 0644]
doc/pod/checklist.pod [new file with mode: 0644]
doc/pod/ckpasswd.pod [new file with mode: 0644]
doc/pod/control.ctl.pod [new file with mode: 0644]
doc/pod/convdate.pod [new file with mode: 0644]
doc/pod/cycbuff.conf.pod [new file with mode: 0644]
doc/pod/distrib.pats.pod [new file with mode: 0644]
doc/pod/domain.pod [new file with mode: 0644]
doc/pod/expire.ctl.pod [new file with mode: 0644]
doc/pod/expireover.pod [new file with mode: 0644]
doc/pod/external-auth.pod [new file with mode: 0644]
doc/pod/fastrm.pod [new file with mode: 0644]
doc/pod/grephistory.pod [new file with mode: 0644]
doc/pod/hacking.pod [new file with mode: 0644]
doc/pod/hook-perl.pod [new file with mode: 0644]
doc/pod/hook-python.pod [new file with mode: 0644]
doc/pod/ident.pod [new file with mode: 0644]
doc/pod/inews.pod [new file with mode: 0644]
doc/pod/inn.conf.pod [new file with mode: 0644]
doc/pod/innconfval.pod [new file with mode: 0644]
doc/pod/innd.pod [new file with mode: 0644]
doc/pod/inndf.pod [new file with mode: 0644]
doc/pod/inndstart.pod [new file with mode: 0644]
doc/pod/innmail.pod [new file with mode: 0644]
doc/pod/innupgrade.pod [new file with mode: 0644]
doc/pod/install.pod [new file with mode: 0644]
doc/pod/libauth.pod [new file with mode: 0644]
doc/pod/libinnhist.pod [new file with mode: 0644]
doc/pod/list.pod [new file with mode: 0644]
doc/pod/mailpost.pod [new file with mode: 0644]
doc/pod/makehistory.pod [new file with mode: 0644]
doc/pod/motd.news.pod [new file with mode: 0644]
doc/pod/news.pod [new file with mode: 0644]
doc/pod/newsfeeds.pod [new file with mode: 0644]
doc/pod/ninpaths.pod [new file with mode: 0644]
doc/pod/nnrpd.pod [new file with mode: 0644]
doc/pod/ovdb.pod [new file with mode: 0644]
doc/pod/ovdb_init.pod [new file with mode: 0644]
doc/pod/ovdb_monitor.pod [new file with mode: 0644]
doc/pod/ovdb_server.pod [new file with mode: 0644]
doc/pod/ovdb_stat.pod [new file with mode: 0644]
doc/pod/passwd.nntp.pod [new file with mode: 0644]
doc/pod/pullnews.pod [new file with mode: 0644]
doc/pod/qio.pod [new file with mode: 0644]
doc/pod/radius.conf.pod [new file with mode: 0644]
doc/pod/radius.pod [new file with mode: 0644]
doc/pod/rc.news.pod [new file with mode: 0644]
doc/pod/readers.conf.pod [new file with mode: 0644]
doc/pod/readme.pod [new file with mode: 0644]
doc/pod/sasl.conf.pod [new file with mode: 0644]
doc/pod/sendinpaths.pod [new file with mode: 0644]
doc/pod/simpleftp.pod [new file with mode: 0644]
doc/pod/sm.pod [new file with mode: 0644]
doc/pod/subscriptions.pod [new file with mode: 0644]
doc/pod/tdx-util.pod [new file with mode: 0644]
doc/pod/tst.pod [new file with mode: 0644]
doc/pod/uwildmat.pod [new file with mode: 0644]
doc/sample-control [new file with mode: 0644]
expire/Makefile [new file with mode: 0644]
expire/convdate.c [new file with mode: 0644]
expire/expire.c [new file with mode: 0644]
expire/expireover.c [new file with mode: 0644]
expire/expirerm.in [new file with mode: 0644]
expire/fastrm.c [new file with mode: 0644]
expire/grephistory.c [new file with mode: 0644]
expire/makedbz.c [new file with mode: 0644]
expire/makehistory.c [new file with mode: 0644]
expire/prunehistory.c [new file with mode: 0644]
extra/active [new file with mode: 0644]
extra/buildinnkeyring [new file with mode: 0644]
extra/bunbatch [new file with mode: 0644]
extra/dh_cloneconf [new file with mode: 0644]
extra/ginpaths2 [new file with mode: 0644]
extra/newsgroups [new file with mode: 0644]
extra/sasl.conf [new file with mode: 0644]
extra/send-uucp.cf [new file with mode: 0644]
frontends/Makefile [new file with mode: 0644]
frontends/cnfsheadconf.in [new file with mode: 0644]
frontends/cnfsstat.in [new file with mode: 0644]
frontends/ctlinnd.c [new file with mode: 0644]
frontends/decode.c [new file with mode: 0644]
frontends/encode.c [new file with mode: 0644]
frontends/feedone.c [new file with mode: 0644]
frontends/getlist.c [new file with mode: 0644]
frontends/inews.c [new file with mode: 0644]
frontends/innconfval.c [new file with mode: 0644]
frontends/mailpost.in [new file with mode: 0644]
frontends/ovdb_init.c [new file with mode: 0644]
frontends/ovdb_monitor.c [new file with mode: 0644]
frontends/ovdb_server.c [new file with mode: 0644]
frontends/ovdb_stat.c [new file with mode: 0644]
frontends/pullnews.in [new file with mode: 0644]
frontends/rnews.c [new file with mode: 0644]
frontends/scanspool.in [new file with mode: 0644]
frontends/sm.c [new file with mode: 0644]
frontends/sys2nf.c [new file with mode: 0644]
history/Make.methods [new file with mode: 0644]
history/Makefile [new file with mode: 0644]
history/buildconfig.in [new file with mode: 0644]
history/his.c [new file with mode: 0644]
history/hisinterface.h [new file with mode: 0644]
history/hisv6/hismethod.config [new file with mode: 0644]
history/hisv6/hisv6-private.h [new file with mode: 0644]
history/hisv6/hisv6.c [new file with mode: 0644]
history/hisv6/hisv6.h [new file with mode: 0644]
include/Makefile [new file with mode: 0644]
include/acconfig.h [new file with mode: 0644]
include/clibrary.h [new file with mode: 0644]
include/conffile.h [new file with mode: 0644]
include/config.h.in [new file with mode: 0644]
include/dbz.h [new file with mode: 0644]
include/inn/buffer.h [new file with mode: 0644]
include/inn/confparse.h [new file with mode: 0644]
include/inn/defines.h [new file with mode: 0644]
include/inn/hashtab.h [new file with mode: 0644]
include/inn/history.h [new file with mode: 0644]
include/inn/innconf.h [new file with mode: 0644]
include/inn/list.h [new file with mode: 0644]
include/inn/md5.h [new file with mode: 0644]
include/inn/messages.h [new file with mode: 0644]
include/inn/mmap.h [new file with mode: 0644]
include/inn/qio.h [new file with mode: 0644]
include/inn/sequence.h [new file with mode: 0644]
include/inn/timer.h [new file with mode: 0644]
include/inn/tst.h [new file with mode: 0644]
include/inn/vector.h [new file with mode: 0644]
include/inn/wire.h [new file with mode: 0644]
include/inndcomm.h [new file with mode: 0644]
include/innperl.h [new file with mode: 0644]
include/libinn.h [new file with mode: 0644]
include/nntp.h [new file with mode: 0644]
include/ov.h [new file with mode: 0644]
include/paths.h.in [new file with mode: 0644]
include/portable/mmap.h [new file with mode: 0644]
include/portable/setproctitle.h [new file with mode: 0644]
include/portable/socket.h [new file with mode: 0644]
include/portable/time.h [new file with mode: 0644]
include/portable/wait.h [new file with mode: 0644]
include/ppport.h [new file with mode: 0644]
include/storage.h [new file with mode: 0644]
innd/Makefile [new file with mode: 0644]
innd/art.c [new file with mode: 0644]
innd/cc.c [new file with mode: 0644]
innd/chan.c [new file with mode: 0644]
innd/icd.c [new file with mode: 0644]
innd/innd.c [new file with mode: 0644]
innd/innd.h [new file with mode: 0644]
innd/inndstart.c [new file with mode: 0644]
innd/keywords.c [new file with mode: 0644]
innd/lc.c [new file with mode: 0644]
innd/nc.c [new file with mode: 0644]
innd/newsfeeds.c [new file with mode: 0644]
innd/ng.c [new file with mode: 0644]
innd/perl.c [new file with mode: 0644]
innd/proc.c [new file with mode: 0644]
innd/python.c [new file with mode: 0644]
innd/rc.c [new file with mode: 0644]
innd/site.c [new file with mode: 0644]
innd/status.c [new file with mode: 0644]
innd/tcl.c [new file with mode: 0644]
innd/util.c [new file with mode: 0644]
innd/wip.c [new file with mode: 0644]
innfeed/Makefile [new file with mode: 0644]
innfeed/README [new file with mode: 0644]
innfeed/article.c [new file with mode: 0644]
innfeed/article.h [new file with mode: 0644]
innfeed/buffer.c [new file with mode: 0644]
innfeed/buffer.h [new file with mode: 0644]
innfeed/config_l.c [new file with mode: 0644]
innfeed/configfile.h [new file with mode: 0644]
innfeed/configfile.l [new file with mode: 0644]
innfeed/configfile.y [new file with mode: 0644]
innfeed/connection.c [new file with mode: 0644]
innfeed/connection.h [new file with mode: 0644]
innfeed/endpoint.c [new file with mode: 0644]
innfeed/endpoint.h [new file with mode: 0644]
innfeed/host.c [new file with mode: 0644]
innfeed/host.h [new file with mode: 0644]
innfeed/imap_connection.c [new file with mode: 0644]
innfeed/innfeed-convcfg.in [new file with mode: 0644]
innfeed/innfeed.h [new file with mode: 0644]
innfeed/innlistener.c [new file with mode: 0644]
innfeed/innlistener.h [new file with mode: 0644]
innfeed/main.c [new file with mode: 0644]
innfeed/misc.c [new file with mode: 0644]
innfeed/misc.h [new file with mode: 0644]
innfeed/procbatch.in [new file with mode: 0644]
innfeed/startinnfeed.c [new file with mode: 0644]
innfeed/tape.c [new file with mode: 0644]
innfeed/tape.h [new file with mode: 0644]
innfeed/testListener.pl [new file with mode: 0644]
lib/Makefile [new file with mode: 0644]
lib/buffer.c [new file with mode: 0644]
lib/cleanfrom.c [new file with mode: 0644]
lib/clientactive.c [new file with mode: 0644]
lib/clientlib.c [new file with mode: 0644]
lib/concat.c [new file with mode: 0644]
lib/conffile.c [new file with mode: 0644]
lib/confparse.c [new file with mode: 0644]
lib/daemonize.c [new file with mode: 0644]
lib/date.c [new file with mode: 0644]
lib/dbz.c [new file with mode: 0644]
lib/defdist.c [new file with mode: 0644]
lib/fdflags.c [new file with mode: 0644]
lib/fdlimit.c [new file with mode: 0644]
lib/fseeko.c [new file with mode: 0644]
lib/ftello.c [new file with mode: 0644]
lib/genid.c [new file with mode: 0644]
lib/getfqdn.c [new file with mode: 0644]
lib/getmodaddr.c [new file with mode: 0644]
lib/getpagesize.c [new file with mode: 0644]
lib/gettime.c [new file with mode: 0644]
lib/hash.c [new file with mode: 0644]
lib/hashtab.c [new file with mode: 0644]
lib/hstrerror.c [new file with mode: 0644]
lib/inet_aton.c [new file with mode: 0644]
lib/inet_ntoa.c [new file with mode: 0644]
lib/innconf.c [new file with mode: 0644]
lib/inndcomm.c [new file with mode: 0644]
lib/list.c [new file with mode: 0644]
lib/localopen.c [new file with mode: 0644]
lib/lockfile.c [new file with mode: 0644]
lib/makedir.c [new file with mode: 0644]
lib/md5.c [new file with mode: 0644]
lib/memcmp.c [new file with mode: 0644]
lib/messages.c [new file with mode: 0644]
lib/mkstemp.c [new file with mode: 0644]
lib/mmap.c [new file with mode: 0644]
lib/parsedate.y [new file with mode: 0644]
lib/perl.c [new file with mode: 0644]
lib/pread.c [new file with mode: 0644]
lib/pwrite.c [new file with mode: 0644]
lib/qio.c [new file with mode: 0644]
lib/radix32.c [new file with mode: 0644]
lib/readin.c [new file with mode: 0644]
lib/remopen.c [new file with mode: 0644]
lib/reservedfd.c [new file with mode: 0644]
lib/resource.c [new file with mode: 0644]
lib/sendarticle.c [new file with mode: 0644]
lib/sendpass.c [new file with mode: 0644]
lib/sequence.c [new file with mode: 0644]
lib/setenv.c [new file with mode: 0644]
lib/seteuid.c [new file with mode: 0644]
lib/setproctitle.c [new file with mode: 0644]
lib/snprintf.c [new file with mode: 0644]
lib/sockaddr.c [new file with mode: 0644]
lib/strcasecmp.c [new file with mode: 0644]
lib/strerror.c [new file with mode: 0644]
lib/strlcat.c [new file with mode: 0644]
lib/strlcpy.c [new file with mode: 0644]
lib/strspn.c [new file with mode: 0644]
lib/strtok.c [new file with mode: 0644]
lib/timer.c [new file with mode: 0644]
lib/tst.c [new file with mode: 0644]
lib/uwildmat.c [new file with mode: 0644]
lib/vector.c [new file with mode: 0644]
lib/version.c [new file with mode: 0644]
lib/wire.c [new file with mode: 0644]
lib/xfopena.c [new file with mode: 0644]
lib/xmalloc.c [new file with mode: 0644]
lib/xsignal.c [new file with mode: 0644]
lib/xwrite.c [new file with mode: 0644]
nnrpd/Makefile [new file with mode: 0644]
nnrpd/article.c [new file with mode: 0644]
nnrpd/cache.c [new file with mode: 0644]
nnrpd/cache.h [new file with mode: 0644]
nnrpd/commands.c [new file with mode: 0644]
nnrpd/group.c [new file with mode: 0644]
nnrpd/line.c [new file with mode: 0644]
nnrpd/list.c [new file with mode: 0644]
nnrpd/misc.c [new file with mode: 0644]
nnrpd/newnews.c [new file with mode: 0644]
nnrpd/nnrpd.c [new file with mode: 0644]
nnrpd/nnrpd.h [new file with mode: 0644]
nnrpd/perl.c [new file with mode: 0644]
nnrpd/perm.c [new file with mode: 0644]
nnrpd/post.c [new file with mode: 0644]
nnrpd/post.h [new file with mode: 0644]
nnrpd/python.c [new file with mode: 0644]
nnrpd/sasl_config.c [new file with mode: 0644]
nnrpd/sasl_config.h [new file with mode: 0644]
nnrpd/tls.c [new file with mode: 0644]
nnrpd/tls.h [new file with mode: 0644]
nnrpd/track.c [new file with mode: 0644]
samples/INN.py [new file with mode: 0644]
samples/Makefile [new file with mode: 0644]
samples/active.minimal [new file with mode: 0644]
samples/actsync.cfg [new file with mode: 0644]
samples/actsync.ign [new file with mode: 0644]
samples/buffindexed.conf [new file with mode: 0644]
samples/control.ctl [new file with mode: 0644]
samples/cycbuff.conf [new file with mode: 0644]
samples/distrib.pats [new file with mode: 0644]
samples/expire.ctl [new file with mode: 0644]
samples/filter.tcl [new file with mode: 0644]
samples/filter_innd.pl [new file with mode: 0644]
samples/filter_innd.py [new file with mode: 0644]
samples/filter_nnrpd.pl [new file with mode: 0644]
samples/incoming.conf [new file with mode: 0644]
samples/inn.conf.in [new file with mode: 0644]
samples/innfeed.conf [new file with mode: 0644]
samples/innreport.conf.in [new file with mode: 0644]
samples/innwatch.ctl [new file with mode: 0644]
samples/moderators [new file with mode: 0644]
samples/motd.news [new file with mode: 0644]
samples/news2mail.cf [new file with mode: 0644]
samples/newsfeeds.in [new file with mode: 0644]
samples/newsgroups.minimal [new file with mode: 0644]
samples/nnrpd.py [new file with mode: 0644]
samples/nnrpd.track [new file with mode: 0644]
samples/nnrpd_access.pl.in [new file with mode: 0644]
samples/nnrpd_access.py [new file with mode: 0644]
samples/nnrpd_access_wrapper.pl.in [new file with mode: 0644]
samples/nnrpd_access_wrapper.py [new file with mode: 0644]
samples/nnrpd_auth.pl.in [new file with mode: 0644]
samples/nnrpd_auth.py [new file with mode: 0644]
samples/nnrpd_auth_wrapper.pl.in [new file with mode: 0644]
samples/nnrpd_auth_wrapper.py [new file with mode: 0644]
samples/nnrpd_dynamic.py [new file with mode: 0644]
samples/nnrpd_dynamic_wrapper.py [new file with mode: 0644]
samples/nntpsend.ctl [new file with mode: 0644]
samples/ovdb.conf [new file with mode: 0644]
samples/overview.fmt [new file with mode: 0644]
samples/passwd.nntp [new file with mode: 0644]
samples/radius.conf [new file with mode: 0644]
samples/readers.conf [new file with mode: 0644]
samples/sasl.conf.in [new file with mode: 0644]
samples/startup.tcl [new file with mode: 0644]
samples/startup_innd.pl [new file with mode: 0644]
samples/storage.conf [new file with mode: 0644]
samples/subscriptions [new file with mode: 0644]
scripts/Makefile [new file with mode: 0644]
scripts/inncheck.in [new file with mode: 0644]
scripts/innmail.in [new file with mode: 0644]
scripts/innreport.in [new file with mode: 0644]
scripts/innreport_inn.pm [new file with mode: 0644]
scripts/innshellvars.in [new file with mode: 0644]
scripts/innshellvars.pl.in [new file with mode: 0644]
scripts/innshellvars.tcl.in [new file with mode: 0644]
scripts/innstat.in [new file with mode: 0644]
scripts/innupgrade.in [new file with mode: 0644]
scripts/innwatch.in [new file with mode: 0644]
scripts/news.daily.in [new file with mode: 0644]
scripts/rc.news.in [new file with mode: 0644]
scripts/scanlogs.in [new file with mode: 0644]
scripts/simpleftp.in [new file with mode: 0644]
scripts/tally.control.in [new file with mode: 0644]
scripts/writelog.in [new file with mode: 0644]
site/Makefile [new file with mode: 0644]
site/getsafe.sh [new file with mode: 0644]
storage/Make.methods [new file with mode: 0644]
storage/Makefile [new file with mode: 0644]
storage/buffindexed/buffindexed.c [new file with mode: 0644]
storage/buffindexed/buffindexed.h [new file with mode: 0644]
storage/buffindexed/ovmethod.config [new file with mode: 0644]
storage/buffindexed/ovmethod.mk [new file with mode: 0644]
storage/buildconfig.in [new file with mode: 0644]
storage/cnfs/cnfs-private.h [new file with mode: 0644]
storage/cnfs/cnfs.c [new file with mode: 0644]
storage/cnfs/cnfs.h [new file with mode: 0644]
storage/cnfs/method.config [new file with mode: 0644]
storage/expire.c [new file with mode: 0644]
storage/interface.c [new file with mode: 0644]
storage/interface.h [new file with mode: 0644]
storage/ov.c [new file with mode: 0644]
storage/ovdb/ovdb-private.h [new file with mode: 0644]
storage/ovdb/ovdb.c [new file with mode: 0644]
storage/ovdb/ovdb.h [new file with mode: 0644]
storage/ovdb/ovmethod.config [new file with mode: 0644]
storage/overdata.c [new file with mode: 0644]
storage/ovinterface.h [new file with mode: 0644]
storage/timecaf/README.CAF [new file with mode: 0644]
storage/timecaf/caf.c [new file with mode: 0644]
storage/timecaf/caf.h [new file with mode: 0644]
storage/timecaf/method.config [new file with mode: 0644]
storage/timecaf/timecaf.c [new file with mode: 0644]
storage/timecaf/timecaf.h [new file with mode: 0644]
storage/timehash/method.config [new file with mode: 0644]
storage/timehash/timehash.c [new file with mode: 0644]
storage/timehash/timehash.h [new file with mode: 0644]
storage/tradindexed/ovmethod.config [new file with mode: 0644]
storage/tradindexed/ovmethod.mk [new file with mode: 0644]
storage/tradindexed/tdx-cache.c [new file with mode: 0644]
storage/tradindexed/tdx-data.c [new file with mode: 0644]
storage/tradindexed/tdx-group.c [new file with mode: 0644]
storage/tradindexed/tdx-private.h [new file with mode: 0644]
storage/tradindexed/tdx-structure.h [new file with mode: 0644]
storage/tradindexed/tdx-util.c [new file with mode: 0644]
storage/tradindexed/tradindexed.c [new file with mode: 0644]
storage/tradindexed/tradindexed.h [new file with mode: 0644]
storage/tradspool/README.tradspool [new file with mode: 0644]
storage/tradspool/method.config [new file with mode: 0644]
storage/tradspool/tradspool.c [new file with mode: 0644]
storage/tradspool/tradspool.h [new file with mode: 0644]
storage/trash/method.config [new file with mode: 0644]
storage/trash/trash.c [new file with mode: 0644]
storage/trash/trash.h [new file with mode: 0644]
support/config.guess [new file with mode: 0755]
support/config.sub [new file with mode: 0755]
support/fixscript.in [new file with mode: 0644]
support/indent [new file with mode: 0755]
support/install-sh [new file with mode: 0755]
support/ltmain.sh [new file with mode: 0644]
support/makedepend [new file with mode: 0755]
support/mkchangelog [new file with mode: 0755]
support/mkmanifest [new file with mode: 0755]
support/mksnapshot [new file with mode: 0755]
support/mksystem [new file with mode: 0755]
support/mkversion [new file with mode: 0755]
tests/Makefile [new file with mode: 0644]
tests/TESTS [new file with mode: 0644]
tests/authprogs/ckpasswd.t [new file with mode: 0755]
tests/authprogs/domain.t [new file with mode: 0755]
tests/authprogs/passwd [new file with mode: 0644]
tests/lib/articles/no-body [new file with mode: 0644]
tests/lib/articles/strange [new file with mode: 0644]
tests/lib/articles/truncated [new file with mode: 0644]
tests/lib/buffer-t.c [new file with mode: 0644]
tests/lib/concat-t.c [new file with mode: 0644]
tests/lib/config/errors [new file with mode: 0644]
tests/lib/config/line-endings [new file with mode: 0644]
tests/lib/config/no-newline [new file with mode: 0644]
tests/lib/config/null [new file with mode: 0644]
tests/lib/config/simple [new file with mode: 0644]
tests/lib/config/valid [new file with mode: 0644]
tests/lib/config/warn-bool [new file with mode: 0644]
tests/lib/config/warn-int [new file with mode: 0644]
tests/lib/config/warnings [new file with mode: 0644]
tests/lib/confparse-t.c [new file with mode: 0644]
tests/lib/date-t.c [new file with mode: 0644]
tests/lib/fakewrite.c [new file with mode: 0644]
tests/lib/hash-t.c [new file with mode: 0644]
tests/lib/hashtab-t.c [new file with mode: 0644]
tests/lib/hstrerror-t.c [new file with mode: 0644]
tests/lib/inet_aton-t.c [new file with mode: 0644]
tests/lib/inet_ntoa-t.c [new file with mode: 0644]
tests/lib/innconf-t.c [new file with mode: 0644]
tests/lib/list-t.c [new file with mode: 0644]
tests/lib/md5-t.c [new file with mode: 0644]
tests/lib/memcmp-t.c [new file with mode: 0644]
tests/lib/messages-t.c [new file with mode: 0644]
tests/lib/mkstemp-t.c [new file with mode: 0644]
tests/lib/pread-t.c [new file with mode: 0644]
tests/lib/pwrite-t.c [new file with mode: 0644]
tests/lib/qio-t.c [new file with mode: 0644]
tests/lib/setenv-t.c [new file with mode: 0644]
tests/lib/setenv.t [new file with mode: 0755]
tests/lib/snprintf-t.c [new file with mode: 0644]
tests/lib/strerror-t.c [new file with mode: 0644]
tests/lib/strlcat-t.c [new file with mode: 0644]
tests/lib/strlcpy-t.c [new file with mode: 0644]
tests/lib/tst-t.c [new file with mode: 0644]
tests/lib/uwildmat-t.c [new file with mode: 0644]
tests/lib/vector-t.c [new file with mode: 0644]
tests/lib/wire-t.c [new file with mode: 0644]
tests/lib/xmalloc.c [new file with mode: 0644]
tests/lib/xmalloc.t [new file with mode: 0755]
tests/lib/xwrite-t.c [new file with mode: 0644]
tests/libtest.c [new file with mode: 0644]
tests/libtest.h [new file with mode: 0644]
tests/overview/data/basic [new file with mode: 0644]
tests/overview/data/bogus [new file with mode: 0644]
tests/overview/data/high-numbered [new file with mode: 0644]
tests/overview/data/reversed [new file with mode: 0644]
tests/overview/munge-data [new file with mode: 0755]
tests/overview/tradindexed-t.c [new file with mode: 0644]
tests/runtests.c [new file with mode: 0644]
tests/util/convdate.t [new file with mode: 0755]

diff --git a/CONTRIBUTORS b/CONTRIBUTORS
new file mode 100644 (file)
index 0000000..504329f
--- /dev/null
@@ -0,0 +1,261 @@
+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
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..397156e
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,238 @@
+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).
+
diff --git a/HACKING b/HACKING
new file mode 100644 (file)
index 0000000..87a76e3
--- /dev/null
+++ b/HACKING
@@ -0,0 +1,708 @@
+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.
+
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..eceb489
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,1527 @@
+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.
+
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..4d0fec5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,87 @@
+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.
diff --git a/MANIFEST b/MANIFEST
new file mode 100644 (file)
index 0000000..d93dada
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,745 @@
+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
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..13e27b5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,218 @@
+##  $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
diff --git a/Makefile.global.in b/Makefile.global.in
new file mode 100644 (file)
index 0000000..3f9cf58
--- /dev/null
@@ -0,0 +1,286 @@
+##  $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@
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..2ad3e94
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,860 @@
+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).
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..722abc5
--- /dev/null
+++ b/README
@@ -0,0 +1,288 @@
+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>
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..28b655b
--- /dev/null
+++ b/TODO
@@ -0,0 +1,847 @@
+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.
diff --git a/aclocal.m4 b/aclocal.m4
new file mode 100644 (file)
index 0000000..066bf6a
--- /dev/null
@@ -0,0 +1,3573 @@
+# 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])
diff --git a/authprogs/Makefile b/authprogs/Makefile
new file mode 100644 (file)
index 0000000..efb2751
--- /dev/null
@@ -0,0 +1,115 @@
+##  $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
diff --git a/authprogs/auth_krb5.c b/authprogs/auth_krb5.c
new file mode 100644 (file)
index 0000000..1088b8b
--- /dev/null
@@ -0,0 +1,217 @@
+/*  $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");
+    }
+}
diff --git a/authprogs/auth_smb.c b/authprogs/auth_smb.c
new file mode 100644 (file)
index 0000000..34fcc55
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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;
+}
diff --git a/authprogs/ckpasswd.c b/authprogs/ckpasswd.c
new file mode 100644 (file)
index 0000000..e8f1db1
--- /dev/null
@@ -0,0 +1,411 @@
+/*  $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);
+}
diff --git a/authprogs/domain.c b/authprogs/domain.c
new file mode 100644 (file)
index 0000000..e4e0f4f
--- /dev/null
@@ -0,0 +1,49 @@
+/*  $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;
+}
diff --git a/authprogs/ident.c b/authprogs/ident.c
new file mode 100644 (file)
index 0000000..ac728e1
--- /dev/null
@@ -0,0 +1,179 @@
+/*  $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);
+}
diff --git a/authprogs/libauth.c b/authprogs/libauth.c
new file mode 100644 (file)
index 0000000..c99dcd9
--- /dev/null
@@ -0,0 +1,216 @@
+/*  $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);
+}
+
diff --git a/authprogs/libauth.h b/authprogs/libauth.h
new file mode 100644 (file)
index 0000000..faa6520
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+**
+**  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*);
+
+
diff --git a/authprogs/radius.c b/authprogs/radius.c
new file mode 100644 (file)
index 0000000..e6da348
--- /dev/null
@@ -0,0 +1,564 @@
+/*  $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);
+}
diff --git a/authprogs/smbval/Makefile b/authprogs/smbval/Makefile
new file mode 100644 (file)
index 0000000..6bb6ef1
--- /dev/null
@@ -0,0 +1,51 @@
+##  $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
diff --git a/authprogs/smbval/byteorder.h b/authprogs/smbval/byteorder.h
new file mode 100644 (file)
index 0000000..9bea2b2
--- /dev/null
@@ -0,0 +1,70 @@
+/* 
+   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
diff --git a/authprogs/smbval/rfcnb-common.h b/authprogs/smbval/rfcnb-common.h
new file mode 100644 (file)
index 0000000..ba09d7c
--- /dev/null
@@ -0,0 +1,36 @@
+/* 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);
diff --git a/authprogs/smbval/rfcnb-error.h b/authprogs/smbval/rfcnb-error.h
new file mode 100644 (file)
index 0000000..afa1328
--- /dev/null
@@ -0,0 +1,48 @@
+/* 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                          */
diff --git a/authprogs/smbval/rfcnb-io.c b/authprogs/smbval/rfcnb-io.c
new file mode 100644 (file)
index 0000000..3f030fa
--- /dev/null
@@ -0,0 +1,310 @@
+/* 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));
+}
diff --git a/authprogs/smbval/rfcnb-io.h b/authprogs/smbval/rfcnb-io.h
new file mode 100644 (file)
index 0000000..753c7ae
--- /dev/null
@@ -0,0 +1,28 @@
+/* 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);
diff --git a/authprogs/smbval/rfcnb-priv.h b/authprogs/smbval/rfcnb-priv.h
new file mode 100644 (file)
index 0000000..a6b9da8
--- /dev/null
@@ -0,0 +1,115 @@
+/* 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
diff --git a/authprogs/smbval/rfcnb-util.c b/authprogs/smbval/rfcnb-util.c
new file mode 100644 (file)
index 0000000..9e57e4e
--- /dev/null
@@ -0,0 +1,331 @@
+/* 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;
+    }
+}
diff --git a/authprogs/smbval/rfcnb-util.h b/authprogs/smbval/rfcnb-util.h
new file mode 100644 (file)
index 0000000..1af7e5e
--- /dev/null
@@ -0,0 +1,42 @@
+/* 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);
+
diff --git a/authprogs/smbval/rfcnb.h b/authprogs/smbval/rfcnb.h
new file mode 100644 (file)
index 0000000..8c2ea1c
--- /dev/null
@@ -0,0 +1,48 @@
+/* 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);
diff --git a/authprogs/smbval/session.c b/authprogs/smbval/session.c
new file mode 100644 (file)
index 0000000..ec35bcd
--- /dev/null
@@ -0,0 +1,304 @@
+/* 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)));
+
+}
diff --git a/authprogs/smbval/smbdes.c b/authprogs/smbval/smbdes.c
new file mode 100644 (file)
index 0000000..e4f8280
--- /dev/null
@@ -0,0 +1,337 @@
+/* 
+   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);
+}
+
diff --git a/authprogs/smbval/smbencrypt.c b/authprogs/smbval/smbencrypt.c
new file mode 100644 (file)
index 0000000..4cae973
--- /dev/null
@@ -0,0 +1,60 @@
+/* 
+   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++;
+    }
+  }
+}                      
diff --git a/authprogs/smbval/smblib-common.h b/authprogs/smbval/smblib-common.h
new file mode 100644 (file)
index 0000000..b8441e0
--- /dev/null
@@ -0,0 +1,69 @@
+/* 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                       */
diff --git a/authprogs/smbval/smblib-priv.h b/authprogs/smbval/smblib-priv.h
new file mode 100644 (file)
index 0000000..155b66b
--- /dev/null
@@ -0,0 +1,249 @@
+/* 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 *);
diff --git a/authprogs/smbval/smblib-util.c b/authprogs/smbval/smblib-util.c
new file mode 100644 (file)
index 0000000..27f5619
--- /dev/null
@@ -0,0 +1,332 @@
+/* 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 "." */
+
+
+}
diff --git a/authprogs/smbval/smblib.c b/authprogs/smbval/smblib.c
new file mode 100644 (file)
index 0000000..06c8509
--- /dev/null
@@ -0,0 +1,379 @@
+/* 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);
+
+}
diff --git a/authprogs/smbval/smblib.h b/authprogs/smbval/smblib.h
new file mode 100644 (file)
index 0000000..a93255f
--- /dev/null
@@ -0,0 +1,50 @@
+/* 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);
diff --git a/authprogs/smbval/valid.c b/authprogs/smbval/valid.c
new file mode 100644 (file)
index 0000000..425b14f
--- /dev/null
@@ -0,0 +1,48 @@
+#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);
+}
diff --git a/authprogs/smbval/valid.h b/authprogs/smbval/valid.h
new file mode 100644 (file)
index 0000000..00d068b
--- /dev/null
@@ -0,0 +1,12 @@
+#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
diff --git a/backends/Makefile b/backends/Makefile
new file mode 100644 (file)
index 0000000..f419e3f
--- /dev/null
@@ -0,0 +1,183 @@
+##  $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
diff --git a/backends/actmerge.in b/backends/actmerge.in
new file mode 100644 (file)
index 0000000..237a332
--- /dev/null
@@ -0,0 +1,216 @@
+#! /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
diff --git a/backends/actsync.c b/backends/actsync.c
new file mode 100644 (file)
index 0000000..41c35e0
--- /dev/null
@@ -0,0 +1,2766 @@
+/* @(#) $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;
+}
diff --git a/backends/actsyncd.in b/backends/actsyncd.in
new file mode 100644 (file)
index 0000000..a88f25d
--- /dev/null
@@ -0,0 +1,256 @@
+#! /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
diff --git a/backends/archive.c b/backends/archive.c
new file mode 100644 (file)
index 0000000..73a7970
--- /dev/null
@@ -0,0 +1,653 @@
+/*  $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 */
+}
diff --git a/backends/batcher.c b/backends/batcher.c
new file mode 100644 (file)
index 0000000..0595778
--- /dev/null
@@ -0,0 +1,428 @@
+/*  $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;
+}
diff --git a/backends/buffchan.c b/backends/buffchan.c
new file mode 100644 (file)
index 0000000..34c9e5b
--- /dev/null
@@ -0,0 +1,483 @@
+/*  $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 */
+}
diff --git a/backends/crosspost.c b/backends/crosspost.c
new file mode 100644 (file)
index 0000000..bef4fb2
--- /dev/null
@@ -0,0 +1,338 @@
+/*  $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 */
+}
diff --git a/backends/cvtbatch.c b/backends/cvtbatch.c
new file mode 100644 (file)
index 0000000..ba57ef8
--- /dev/null
@@ -0,0 +1,128 @@
+/*  $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 */
+}
diff --git a/backends/filechan.c b/backends/filechan.c
new file mode 100644 (file)
index 0000000..93e81a8
--- /dev/null
@@ -0,0 +1,132 @@
+/*  $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 */
+}
diff --git a/backends/inndf.c b/backends/inndf.c
new file mode 100644 (file)
index 0000000..6a85c86
--- /dev/null
@@ -0,0 +1,335 @@
+/*  $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);
+}
diff --git a/backends/innxbatch.c b/backends/innxbatch.c
new file mode 100644 (file)
index 0000000..9ebfe9c
--- /dev/null
@@ -0,0 +1,550 @@
+/*  $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;
+}
diff --git a/backends/innxmit.c b/backends/innxmit.c
new file mode 100644 (file)
index 0000000..475ce63
--- /dev/null
@@ -0,0 +1,1457 @@
+/*  $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;
+}
diff --git a/backends/map.c b/backends/map.c
new file mode 100644 (file)
index 0000000..5d5bcb9
--- /dev/null
@@ -0,0 +1,99 @@
+/*  $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;
+}
diff --git a/backends/map.h b/backends/map.h
new file mode 100644 (file)
index 0000000..c6fd6e7
--- /dev/null
@@ -0,0 +1,7 @@
+/*  $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 */
diff --git a/backends/mod-active.in b/backends/mod-active.in
new file mode 100644 (file)
index 0000000..4360e0b
--- /dev/null
@@ -0,0 +1,115 @@
+#! /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";
+  }
+}
diff --git a/backends/news2mail.in b/backends/news2mail.in
new file mode 100644 (file)
index 0000000..5f22231
--- /dev/null
@@ -0,0 +1,153 @@
+#! /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);
+       }
diff --git a/backends/ninpaths.c b/backends/ninpaths.c
new file mode 100644 (file)
index 0000000..95f397d
--- /dev/null
@@ -0,0 +1,519 @@
+/*  $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;
+}
diff --git a/backends/nntpget.c b/backends/nntpget.c
new file mode 100644 (file)
index 0000000..9d40920
--- /dev/null
@@ -0,0 +1,469 @@
+/*  $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 */
+}
diff --git a/backends/nntpsend.in b/backends/nntpsend.in
new file mode 100644 (file)
index 0000000..eb68718
--- /dev/null
@@ -0,0 +1,472 @@
+#! /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
diff --git a/backends/overchan.c b/backends/overchan.c
new file mode 100644 (file)
index 0000000..30bb028
--- /dev/null
@@ -0,0 +1,142 @@
+/*  $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 */
+}
diff --git a/backends/send-ihave.in b/backends/send-ihave.in
new file mode 100644 (file)
index 0000000..f1ba03c
--- /dev/null
@@ -0,0 +1,95 @@
+#! /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`"
diff --git a/backends/send-nntp.in b/backends/send-nntp.in
new file mode 100644 (file)
index 0000000..018eb6d
--- /dev/null
@@ -0,0 +1,88 @@
+#! /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`"
diff --git a/backends/send-uucp.in b/backends/send-uucp.in
new file mode 100644 (file)
index 0000000..8ba2937
--- /dev/null
@@ -0,0 +1,382 @@
+#!/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
diff --git a/backends/sendinpaths.in b/backends/sendinpaths.in
new file mode 100644 (file)
index 0000000..aedf451
--- /dev/null
@@ -0,0 +1,44 @@
+#!/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
diff --git a/backends/sendxbatches.in b/backends/sendxbatches.in
new file mode 100644 (file)
index 0000000..ee8387b
--- /dev/null
@@ -0,0 +1,39 @@
+#! /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" $*
diff --git a/backends/shlock.c b/backends/shlock.c
new file mode 100644 (file)
index 0000000..bb4c503
--- /dev/null
@@ -0,0 +1,204 @@
+/*  $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;
+}
diff --git a/backends/shrinkfile.c b/backends/shrinkfile.c
new file mode 100644 (file)
index 0000000..7702ff2
--- /dev/null
@@ -0,0 +1,390 @@
+/*  $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 */
+}
diff --git a/configure b/configure
new file mode 100755 (executable)
index 0000000..c4cf14e
--- /dev/null
+++ b/configure
@@ -0,0 +1,11907 @@
+#! /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
diff --git a/configure.in b/configure.in
new file mode 100644 (file)
index 0000000..5301913
--- /dev/null
@@ -0,0 +1,1888 @@
+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
diff --git a/contrib/Makefile b/contrib/Makefile
new file mode 100644 (file)
index 0000000..f09ab09
--- /dev/null
@@ -0,0 +1,56 @@
+##  $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
diff --git a/contrib/README b/contrib/README
new file mode 100644 (file)
index 0000000..e7e8866
--- /dev/null
@@ -0,0 +1,150 @@
+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".
diff --git a/contrib/archivegz.in b/contrib/archivegz.in
new file mode 100644 (file)
index 0000000..e4f06b7
--- /dev/null
@@ -0,0 +1,334 @@
+#!/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 = &regexp_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.
diff --git a/contrib/auth_pass.README b/contrib/auth_pass.README
new file mode 100644 (file)
index 0000000..6919fb2
--- /dev/null
@@ -0,0 +1,75 @@
+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 #####
diff --git a/contrib/auth_pass.c b/contrib/auth_pass.c
new file mode 100644 (file)
index 0000000..7ecf0a1
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ *      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);
+}
diff --git a/contrib/backlogstat.in b/contrib/backlogstat.in
new file mode 100644 (file)
index 0000000..70da166
--- /dev/null
@@ -0,0 +1,118 @@
+#!/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;
+}
+
+
diff --git a/contrib/backupfeed.in b/contrib/backupfeed.in
new file mode 100644 (file)
index 0000000..815fd74
--- /dev/null
@@ -0,0 +1,249 @@
+#! /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;
+}
diff --git a/contrib/cleannewsgroups.in b/contrib/cleannewsgroups.in
new file mode 100644 (file)
index 0000000..daa406b
--- /dev/null
@@ -0,0 +1,45 @@
+#! /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";
diff --git a/contrib/count_overview.pl b/contrib/count_overview.pl
new file mode 100755 (executable)
index 0000000..910938e
--- /dev/null
@@ -0,0 +1,27 @@
+#!/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};
+}
diff --git a/contrib/delayer.in b/contrib/delayer.in
new file mode 100644 (file)
index 0000000..4528d96
--- /dev/null
@@ -0,0 +1,71 @@
+#!/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;
+       
+}
+
diff --git a/contrib/expirectl.c b/contrib/expirectl.c
new file mode 100644 (file)
index 0000000..2224ad7
--- /dev/null
@@ -0,0 +1,306 @@
+/*
+ * 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.
+
+*/
diff --git a/contrib/findreadgroups.in b/contrib/findreadgroups.in
new file mode 100644 (file)
index 0000000..4c5e8ff
--- /dev/null
@@ -0,0 +1,38 @@
+#!/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);
diff --git a/contrib/fixhist b/contrib/fixhist
new file mode 100755 (executable)
index 0000000..0541a00
--- /dev/null
@@ -0,0 +1,89 @@
+#!/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";
+}
diff --git a/contrib/innconfcheck b/contrib/innconfcheck
new file mode 100755 (executable)
index 0000000..83a19d0
--- /dev/null
@@ -0,0 +1,125 @@
+#!/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
diff --git a/contrib/makeexpctl.in b/contrib/makeexpctl.in
new file mode 100644 (file)
index 0000000..320ae1f
--- /dev/null
@@ -0,0 +1,76 @@
+#!/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";
+}
diff --git a/contrib/makestorconf.in b/contrib/makestorconf.in
new file mode 100644 (file)
index 0000000..c92bc83
--- /dev/null
@@ -0,0 +1,56 @@
+#!/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);
diff --git a/contrib/mkbuf b/contrib/mkbuf
new file mode 100755 (executable)
index 0000000..53e326e
--- /dev/null
@@ -0,0 +1,29 @@
+#!/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;
diff --git a/contrib/mlockfile.c b/contrib/mlockfile.c
new file mode 100644 (file)
index 0000000..4bf274f
--- /dev/null
@@ -0,0 +1,182 @@
+/* $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;
+}
diff --git a/contrib/newsresp.c b/contrib/newsresp.c
new file mode 100644 (file)
index 0000000..b2931b7
--- /dev/null
@@ -0,0 +1,297 @@
+/* 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);
+}
diff --git a/contrib/pullart.c b/contrib/pullart.c
new file mode 100644 (file)
index 0000000..6b06099
--- /dev/null
@@ -0,0 +1,297 @@
+/*
+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;
+       }
diff --git a/contrib/reset-cnfs.c b/contrib/reset-cnfs.c
new file mode 100644 (file)
index 0000000..18bb334
--- /dev/null
@@ -0,0 +1,56 @@
+/* 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);
+       }
+    }
+}
diff --git a/contrib/respool.c b/contrib/respool.c
new file mode 100644 (file)
index 0000000..71ed2bd
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+** 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);
+}
diff --git a/contrib/sample.init.script b/contrib/sample.init.script
new file mode 100644 (file)
index 0000000..104ca48
--- /dev/null
@@ -0,0 +1,18 @@
+#!/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
+
diff --git a/contrib/showtoken.in b/contrib/showtoken.in
new file mode 100644 (file)
index 0000000..4f513ce
--- /dev/null
@@ -0,0 +1,95 @@
+#!/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
diff --git a/contrib/stathist.in b/contrib/stathist.in
new file mode 100644 (file)
index 0000000..c34c911
--- /dev/null
@@ -0,0 +1,79 @@
+#!/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);
diff --git a/contrib/thdexpire.in b/contrib/thdexpire.in
new file mode 100644 (file)
index 0000000..93b09bf
--- /dev/null
@@ -0,0 +1,647 @@
+#!/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;
+}
+#-----------------------------------------------------------------------------
diff --git a/contrib/tunefeed.in b/contrib/tunefeed.in
new file mode 100644 (file)
index 0000000..52616ae
--- /dev/null
@@ -0,0 +1,474 @@
+#!/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
diff --git a/control/Makefile b/control/Makefile
new file mode 100644 (file)
index 0000000..0d99310
--- /dev/null
@@ -0,0 +1,51 @@
+##  $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 $? > $@
diff --git a/control/controlbatch.in b/control/controlbatch.in
new file mode 100644 (file)
index 0000000..72035a8
--- /dev/null
@@ -0,0 +1,90 @@
+#! /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}
diff --git a/control/controlchan.in b/control/controlchan.in
new file mode 100644 (file)
index 0000000..7fe7338
--- /dev/null
@@ -0,0 +1,467 @@
+#! /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;
+}
diff --git a/control/docheckgroups.in b/control/docheckgroups.in
new file mode 100644 (file)
index 0000000..cee70d6
--- /dev/null
@@ -0,0 +1,149 @@
+#! /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}/$$*
diff --git a/control/gpgverify.in b/control/gpgverify.in
new file mode 100644 (file)
index 0000000..f3aecea
--- /dev/null
@@ -0,0 +1,237 @@
+#!/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.
diff --git a/control/modules/checkgroups.pl b/control/modules/checkgroups.pl
new file mode 100644 (file)
index 0000000..56366ad
--- /dev/null
@@ -0,0 +1,89 @@
+##  $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;
diff --git a/control/modules/ihave.pl b/control/modules/ihave.pl
new file mode 100644 (file)
index 0000000..a64c235
--- /dev/null
@@ -0,0 +1,58 @@
+##  $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;
diff --git a/control/modules/newgroup.pl b/control/modules/newgroup.pl
new file mode 100644 (file)
index 0000000..94eef22
--- /dev/null
@@ -0,0 +1,214 @@
+##  $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;
diff --git a/control/modules/rmgroup.pl b/control/modules/rmgroup.pl
new file mode 100644 (file)
index 0000000..d78b014
--- /dev/null
@@ -0,0 +1,92 @@
+##  $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;
diff --git a/control/modules/sendme.pl b/control/modules/sendme.pl
new file mode 100644 (file)
index 0000000..d53ab5a
--- /dev/null
@@ -0,0 +1,55 @@
+##  $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;
diff --git a/control/modules/sendsys.pl b/control/modules/sendsys.pl
new file mode 100644 (file)
index 0000000..6f086ba
--- /dev/null
@@ -0,0 +1,64 @@
+##  $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;
diff --git a/control/modules/senduuname.pl b/control/modules/senduuname.pl
new file mode 100644 (file)
index 0000000..a2f71e5
--- /dev/null
@@ -0,0 +1,61 @@
+##  $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;
diff --git a/control/modules/version.pl b/control/modules/version.pl
new file mode 100644 (file)
index 0000000..f06096f
--- /dev/null
@@ -0,0 +1,61 @@
+##  $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;
diff --git a/control/perl-nocem.in b/control/perl-nocem.in
new file mode 100644 (file)
index 0000000..5630d12
--- /dev/null
@@ -0,0 +1,629 @@
+#!/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
diff --git a/control/pgpverify.in b/control/pgpverify.in
new file mode 100644 (file)
index 0000000..feee446
--- /dev/null
@@ -0,0 +1,876 @@
+#! /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:
diff --git a/control/signcontrol.in b/control/signcontrol.in
new file mode 100644 (file)
index 0000000..c1951e7
--- /dev/null
@@ -0,0 +1,600 @@
+#! /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:
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..838c48d
--- /dev/null
@@ -0,0 +1,339 @@
+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
diff --git a/debian/changelog.old b/debian/changelog.old
new file mode 100644 (file)
index 0000000..9adc435
--- /dev/null
@@ -0,0 +1,688 @@
+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.
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..b8626c4
--- /dev/null
@@ -0,0 +1 @@
+4
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..a3fa12f
--- /dev/null
@@ -0,0 +1,90 @@
+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.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..762e6a3
--- /dev/null
@@ -0,0 +1,87 @@
+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'.
+
diff --git a/debian/inn2-dev.files b/debian/inn2-dev.files
new file mode 100644 (file)
index 0000000..229c7f7
--- /dev/null
@@ -0,0 +1,5 @@
+usr/include/inn/
+usr/share/man/man3/
+usr/lib/news/libinn.a
+usr/lib/news/libstorage.a
+usr/lib/news/libinnhist.a
diff --git a/debian/inn2-dev.links b/debian/inn2-dev.links
new file mode 100644 (file)
index 0000000..00ac8c2
--- /dev/null
@@ -0,0 +1,4 @@
+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
diff --git a/debian/inn2-inews.files b/debian/inn2-inews.files
new file mode 100644 (file)
index 0000000..c9833a1
--- /dev/null
@@ -0,0 +1,13 @@
+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
diff --git a/debian/inn2-inews.links b/debian/inn2-inews.links
new file mode 100644 (file)
index 0000000..feaeeb3
--- /dev/null
@@ -0,0 +1,2 @@
+usr/lib/news/bin/inews usr/bin/inews
+usr/lib/news/bin/rnews usr/bin/rnews
diff --git a/debian/inn2.README.Debian b/debian/inn2.README.Debian
new file mode 100644 (file)
index 0000000..61adc45
--- /dev/null
@@ -0,0 +1,102 @@
+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.
+
diff --git a/debian/inn2.cron.d b/debian/inn2.cron.d
new file mode 100644 (file)
index 0000000..1691131
--- /dev/null
@@ -0,0 +1,37 @@
+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 ###################################################################
+
diff --git a/debian/inn2.docs b/debian/inn2.docs
new file mode 100644 (file)
index 0000000..8307807
--- /dev/null
@@ -0,0 +1,10 @@
+CONTRIBUTORS
+INSTALL
+NEWS
+README
+doc/checklist
+doc/external-auth
+doc/history
+doc/hook-perl
+doc/IPv6-info
+doc/compliance-nntp
diff --git a/debian/inn2.examples b/debian/inn2.examples
new file mode 100644 (file)
index 0000000..58759cf
--- /dev/null
@@ -0,0 +1,2 @@
+extra/active
+extra/newsgroups
diff --git a/debian/inn2.init b/debian/inn2.init
new file mode 100644 (file)
index 0000000..ae5b687
--- /dev/null
@@ -0,0 +1,63 @@
+#!/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
diff --git a/debian/inn2.links b/debian/inn2.links
new file mode 100644 (file)
index 0000000..e55863e
--- /dev/null
@@ -0,0 +1,6 @@
+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
diff --git a/debian/inn2.logcheck.ignore.server b/debian/inn2.logcheck.ignore.server
new file mode 100644 (file)
index 0000000..25d80ea
--- /dev/null
@@ -0,0 +1,57 @@
+\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$
diff --git a/debian/inn2.logcheck.violations.ignore b/debian/inn2.logcheck.violations.ignore
new file mode 100644 (file)
index 0000000..4dc7ef7
--- /dev/null
@@ -0,0 +1,10 @@
+^\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\:\,]+)?$
diff --git a/debian/inn2.postinst b/debian/inn2.postinst
new file mode 100644 (file)
index 0000000..40214d0
--- /dev/null
@@ -0,0 +1,181 @@
+#!/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
+
diff --git a/debian/inn2.postrm b/debian/inn2.postrm
new file mode 100644 (file)
index 0000000..53101ec
--- /dev/null
@@ -0,0 +1,14 @@
+#!/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
diff --git a/debian/inn2.preinst b/debian/inn2.preinst
new file mode 100644 (file)
index 0000000..70a1001
--- /dev/null
@@ -0,0 +1,28 @@
+#!/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
diff --git a/debian/inn2.prerm b/debian/inn2.prerm
new file mode 100644 (file)
index 0000000..b111c76
--- /dev/null
@@ -0,0 +1,25 @@
+#!/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
diff --git a/debian/patches/configure-hostname b/debian/patches/configure-hostname
new file mode 100644 (file)
index 0000000..874915a
--- /dev/null
@@ -0,0 +1,11 @@
+--- 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
diff --git a/debian/patches/debian-paths b/debian/patches/debian-paths
new file mode 100644 (file)
index 0000000..38327a5
--- /dev/null
@@ -0,0 +1,10 @@
+--- 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
diff --git a/debian/patches/fix_ad_flag b/debian/patches/fix_ad_flag
new file mode 100644 (file)
index 0000000..a2e4862
--- /dev/null
@@ -0,0 +1,15 @@
+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;
diff --git a/debian/patches/fix_body_regexps b/debian/patches/fix_body_regexps
new file mode 100644 (file)
index 0000000..9a0aedd
--- /dev/null
@@ -0,0 +1,82 @@
+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
diff --git a/debian/patches/no-makedbz-on-install b/debian/patches/no-makedbz-on-install
new file mode 100644 (file)
index 0000000..63b3f65
--- /dev/null
@@ -0,0 +1,11 @@
+--- 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)
diff --git a/debian/patches/nocem-gpg-import b/debian/patches/nocem-gpg-import
new file mode 100644 (file)
index 0000000..0c15727
--- /dev/null
@@ -0,0 +1,28 @@
+--- 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
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644 (file)
index 0000000..9263031
--- /dev/null
@@ -0,0 +1,20 @@
+# 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
diff --git a/debian/patches/typo_inn_conf_man b/debian/patches/typo_inn_conf_man
new file mode 100644 (file)
index 0000000..39c2a91
--- /dev/null
@@ -0,0 +1,11 @@
+--- 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
diff --git a/debian/patches/u_innreport_misc b/debian/patches/u_innreport_misc
new file mode 100644 (file)
index 0000000..0b4e7d2
--- /dev/null
@@ -0,0 +1,136 @@
+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;
+   }
diff --git a/debian/patches/u_right_length b/debian/patches/u_right_length
new file mode 100644 (file)
index 0000000..b8cc72e
--- /dev/null
@@ -0,0 +1,15 @@
+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;
diff --git a/debian/patches/u_status_init_ip b/debian/patches/u_status_init_ip
new file mode 100644 (file)
index 0000000..e91b1e3
--- /dev/null
@@ -0,0 +1,26 @@
+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 =
diff --git a/debian/patches/u_tls_duplicate_reply b/debian/patches/u_tls_duplicate_reply
new file mode 100644 (file)
index 0000000..1b6c01e
--- /dev/null
@@ -0,0 +1,15 @@
+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;
diff --git a/debian/patches/u_xhdr_permissions b/debian/patches/u_xhdr_permissions
new file mode 100644 (file)
index 0000000..8e876af
--- /dev/null
@@ -0,0 +1,49 @@
+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();
diff --git a/debian/patches/u_xover_duplicate_reply b/debian/patches/u_xover_duplicate_reply
new file mode 100644 (file)
index 0000000..88a53b5
--- /dev/null
@@ -0,0 +1,30 @@
+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;
+           }
+       }
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..91d2e12
--- /dev/null
@@ -0,0 +1,198 @@
+#!/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%
diff --git a/debian/watch b/debian/watch
new file mode 100644 (file)
index 0000000..39ffdf8
--- /dev/null
@@ -0,0 +1,3 @@
+version=3
+opts=dversionmangle=s/r$// \
+ftp://ftp.isc.org/isc/inn/inn-([\d\.]+)\.tar\.gz
diff --git a/doc/GPL b/doc/GPL
new file mode 100644 (file)
index 0000000..264509e
--- /dev/null
+++ b/doc/GPL
@@ -0,0 +1,347 @@
+[ 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.
diff --git a/doc/IPv6-info b/doc/IPv6-info
new file mode 100644 (file)
index 0000000..4d5c02a
--- /dev/null
@@ -0,0 +1,47 @@
+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
+
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644 (file)
index 0000000..faff9bf
--- /dev/null
@@ -0,0 +1,38 @@
+##  $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
diff --git a/doc/checklist b/doc/checklist
new file mode 100644 (file)
index 0000000..144c0ee
--- /dev/null
@@ -0,0 +1,210 @@
+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.
+
diff --git a/doc/compliance-nntp b/doc/compliance-nntp
new file mode 100644 (file)
index 0000000..403e104
--- /dev/null
@@ -0,0 +1,320 @@
+$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.
diff --git a/doc/config-design b/doc/config-design
new file mode 100644 (file)
index 0000000..eda5bf3
--- /dev/null
@@ -0,0 +1,121 @@
+$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.)
diff --git a/doc/config-semantics b/doc/config-semantics
new file mode 100644 (file)
index 0000000..49d601e
--- /dev/null
@@ -0,0 +1,79 @@
+$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.
diff --git a/doc/config-syntax b/doc/config-syntax
new file mode 100644 (file)
index 0000000..4863d5d
--- /dev/null
@@ -0,0 +1,242 @@
+$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.
diff --git a/doc/external-auth b/doc/external-auth
new file mode 100644 (file)
index 0000000..28fc847
--- /dev/null
@@ -0,0 +1,111 @@
+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>.
+
diff --git a/doc/history b/doc/history
new file mode 100644 (file)
index 0000000..b5d4cc1
--- /dev/null
@@ -0,0 +1,258 @@
+$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$
diff --git a/doc/hook-perl b/doc/hook-perl
new file mode 100644 (file)
index 0000000..b2e33d8
--- /dev/null
@@ -0,0 +1,597 @@
+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.
diff --git a/doc/hook-python b/doc/hook-python
new file mode 100644 (file)
index 0000000..f6ef5c0
--- /dev/null
@@ -0,0 +1,614 @@
+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 $
+
diff --git a/doc/hook-tcl b/doc/hook-tcl
new file mode 100644 (file)
index 0000000..14c4f00
--- /dev/null
@@ -0,0 +1,99 @@
+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
+
+
diff --git a/doc/man/Makefile b/doc/man/Makefile
new file mode 100644 (file)
index 0000000..3e1a758
--- /dev/null
@@ -0,0 +1,66 @@
+##  $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
diff --git a/doc/man/active.5 b/doc/man/active.5
new file mode 100644 (file)
index 0000000..97a0d62
--- /dev/null
@@ -0,0 +1,221 @@
+.\" 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)
diff --git a/doc/man/active.times.5 b/doc/man/active.times.5
new file mode 100644 (file)
index 0000000..2b95ac5
--- /dev/null
@@ -0,0 +1,163 @@
+.\" 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)
diff --git a/doc/man/actsync.8 b/doc/man/actsync.8
new file mode 100644 (file)
index 0000000..ea06061
--- /dev/null
@@ -0,0 +1,1123 @@
+.\" 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>.
diff --git a/doc/man/actsyncd.8 b/doc/man/actsyncd.8
new file mode 100644 (file)
index 0000000..69d2a45
--- /dev/null
@@ -0,0 +1,9 @@
+.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)
diff --git a/doc/man/archive.8 b/doc/man/archive.8
new file mode 100644 (file)
index 0000000..67b84bf
--- /dev/null
@@ -0,0 +1,151 @@
+.\" $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).
diff --git a/doc/man/auth_krb5.8 b/doc/man/auth_krb5.8
new file mode 100644 (file)
index 0000000..aa1d313
--- /dev/null
@@ -0,0 +1,209 @@
+.\" 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/>.
diff --git a/doc/man/auth_smb.8 b/doc/man/auth_smb.8
new file mode 100644 (file)
index 0000000..42ac1f4
--- /dev/null
@@ -0,0 +1,182 @@
+.\" 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)
diff --git a/doc/man/batcher.8 b/doc/man/batcher.8
new file mode 100644 (file)
index 0000000..3cd2a90
--- /dev/null
@@ -0,0 +1,196 @@
+.\" $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).
diff --git a/doc/man/buffchan.8 b/doc/man/buffchan.8
new file mode 100644 (file)
index 0000000..1fd39c1
--- /dev/null
@@ -0,0 +1,197 @@
+.\" $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).
diff --git a/doc/man/buffindexed.conf.5 b/doc/man/buffindexed.conf.5
new file mode 100644 (file)
index 0000000..42dea50
--- /dev/null
@@ -0,0 +1,120 @@
+.\" $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).
diff --git a/doc/man/ckpasswd.8 b/doc/man/ckpasswd.8
new file mode 100644 (file)
index 0000000..4144211
--- /dev/null
@@ -0,0 +1,311 @@
+.\" 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>.
diff --git a/doc/man/clientlib.3 b/doc/man/clientlib.3
new file mode 100644 (file)
index 0000000..1bbb0a3
--- /dev/null
@@ -0,0 +1,105 @@
+.\" $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).
diff --git a/doc/man/cnfsheadconf.8 b/doc/man/cnfsheadconf.8
new file mode 100644 (file)
index 0000000..8acb5cb
--- /dev/null
@@ -0,0 +1,46 @@
+.\" $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).
diff --git a/doc/man/cnfsstat.8 b/doc/man/cnfsstat.8
new file mode 100644 (file)
index 0000000..ed074e0
--- /dev/null
@@ -0,0 +1,100 @@
+.\" $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).
diff --git a/doc/man/control.ctl.5 b/doc/man/control.ctl.5
new file mode 100644 (file)
index 0000000..fe4f120
--- /dev/null
@@ -0,0 +1,316 @@
+.\" 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).
diff --git a/doc/man/controlchan.8 b/doc/man/controlchan.8
new file mode 100644 (file)
index 0000000..44acfdd
--- /dev/null
@@ -0,0 +1,80 @@
+.\" $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).
diff --git a/doc/man/convdate.1 b/doc/man/convdate.1
new file mode 100644 (file)
index 0000000..9ee526e
--- /dev/null
@@ -0,0 +1,236 @@
+.\" 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).
diff --git a/doc/man/ctlinnd.8 b/doc/man/ctlinnd.8
new file mode 100644 (file)
index 0000000..07bf46f
--- /dev/null
@@ -0,0 +1,661 @@
+.\" $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).
diff --git a/doc/man/cvtbatch.8 b/doc/man/cvtbatch.8
new file mode 100644 (file)
index 0000000..12b9c80
--- /dev/null
@@ -0,0 +1,53 @@
+.\" $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).
diff --git a/doc/man/cycbuff.conf.5 b/doc/man/cycbuff.conf.5
new file mode 100644 (file)
index 0000000..19eba35
--- /dev/null
@@ -0,0 +1,320 @@
+.\" 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)
diff --git a/doc/man/dbz.3 b/doc/man/dbz.3
new file mode 100644 (file)
index 0000000..5029d24
--- /dev/null
@@ -0,0 +1,303 @@
+.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.
diff --git a/doc/man/distrib.pats.5 b/doc/man/distrib.pats.5
new file mode 100644 (file)
index 0000000..cf513e3
--- /dev/null
@@ -0,0 +1,174 @@
+.\" 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)
diff --git a/doc/man/domain.8 b/doc/man/domain.8
new file mode 100644 (file)
index 0000000..8055b6f
--- /dev/null
@@ -0,0 +1,187 @@
+.\" 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)
diff --git a/doc/man/expire.8 b/doc/man/expire.8
new file mode 100644 (file)
index 0000000..a677a35
--- /dev/null
@@ -0,0 +1,251 @@
+.\" $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).
diff --git a/doc/man/expire.ctl.5 b/doc/man/expire.ctl.5
new file mode 100644 (file)
index 0000000..601c001
--- /dev/null
@@ -0,0 +1,319 @@
+.\" 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)
diff --git a/doc/man/expireover.8 b/doc/man/expireover.8
new file mode 100644 (file)
index 0000000..4103a3f
--- /dev/null
@@ -0,0 +1,267 @@
+.\" 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).
diff --git a/doc/man/expirerm.8 b/doc/man/expirerm.8
new file mode 100644 (file)
index 0000000..5738e02
--- /dev/null
@@ -0,0 +1,30 @@
+.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).
+
diff --git a/doc/man/fastrm.1 b/doc/man/fastrm.1
new file mode 100644 (file)
index 0000000..de7cb5f
--- /dev/null
@@ -0,0 +1,308 @@
+.\" 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)
diff --git a/doc/man/filechan.8 b/doc/man/filechan.8
new file mode 100644 (file)
index 0000000..0d7a387
--- /dev/null
@@ -0,0 +1,149 @@
+.\" $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).
diff --git a/doc/man/getlist.1 b/doc/man/getlist.1
new file mode 100644 (file)
index 0000000..fac7402
--- /dev/null
@@ -0,0 +1,99 @@
+.\" $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).
diff --git a/doc/man/grephistory.1 b/doc/man/grephistory.1
new file mode 100644 (file)
index 0000000..b2417b6
--- /dev/null
@@ -0,0 +1,200 @@
+.\" 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)
diff --git a/doc/man/history.5 b/doc/man/history.5
new file mode 100644 (file)
index 0000000..8f7def2
--- /dev/null
@@ -0,0 +1,91 @@
+.\" $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).
diff --git a/doc/man/ident.8 b/doc/man/ident.8
new file mode 100644 (file)
index 0000000..da28b53
--- /dev/null
@@ -0,0 +1,192 @@
+.\" 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)
diff --git a/doc/man/incoming.conf.5 b/doc/man/incoming.conf.5
new file mode 100644 (file)
index 0000000..67f1e13
--- /dev/null
@@ -0,0 +1,190 @@
+.\" $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).
diff --git a/doc/man/inews.1 b/doc/man/inews.1
new file mode 100644 (file)
index 0000000..37c2471
--- /dev/null
@@ -0,0 +1,256 @@
+.\" 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)
diff --git a/doc/man/inn.conf.5 b/doc/man/inn.conf.5
new file mode 100644 (file)
index 0000000..f525e06
--- /dev/null
@@ -0,0 +1,1220 @@
+.\" 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.
diff --git a/doc/man/inncheck.8 b/doc/man/inncheck.8
new file mode 100644 (file)
index 0000000..4ead1db
--- /dev/null
@@ -0,0 +1,172 @@
+.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)
diff --git a/doc/man/innconfval.1 b/doc/man/innconfval.1
new file mode 100644 (file)
index 0000000..0fe9d77
--- /dev/null
@@ -0,0 +1,198 @@
+.\" 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)
diff --git a/doc/man/innd.8 b/doc/man/innd.8
new file mode 100644 (file)
index 0000000..fc07750
--- /dev/null
@@ -0,0 +1,600 @@
+.\" 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).
diff --git a/doc/man/inndcomm.3 b/doc/man/inndcomm.3
new file mode 100644 (file)
index 0000000..0633e97
--- /dev/null
@@ -0,0 +1,142 @@
+.\" $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).
diff --git a/doc/man/inndf.8 b/doc/man/inndf.8
new file mode 100644 (file)
index 0000000..e1272c4
--- /dev/null
@@ -0,0 +1,244 @@
+.\" 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).
diff --git a/doc/man/inndstart.8 b/doc/man/inndstart.8
new file mode 100644 (file)
index 0000000..3c086ef
--- /dev/null
@@ -0,0 +1,394 @@
+.\" 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)
diff --git a/doc/man/innfeed.1 b/doc/man/innfeed.1
new file mode 100644 (file)
index 0000000..db58154
--- /dev/null
@@ -0,0 +1,511 @@
+.\" -*- 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)
diff --git a/doc/man/innfeed.conf.5 b/doc/man/innfeed.conf.5
new file mode 100644 (file)
index 0000000..272cb1a
--- /dev/null
@@ -0,0 +1,732 @@
+.\" -*- 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)
diff --git a/doc/man/innmail.1 b/doc/man/innmail.1
new file mode 100644 (file)
index 0000000..2b89713
--- /dev/null
@@ -0,0 +1,187 @@
+.\" 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).
diff --git a/doc/man/innreport.8 b/doc/man/innreport.8
new file mode 100644 (file)
index 0000000..53c0562
--- /dev/null
@@ -0,0 +1,40 @@
+.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).
diff --git a/doc/man/innstat.8 b/doc/man/innstat.8
new file mode 100644 (file)
index 0000000..194cf78
--- /dev/null
@@ -0,0 +1,23 @@
+.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)
diff --git a/doc/man/innupgrade.8 b/doc/man/innupgrade.8
new file mode 100644 (file)
index 0000000..38cf082
--- /dev/null
@@ -0,0 +1,204 @@
+.\" 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 $
diff --git a/doc/man/innwatch.8 b/doc/man/innwatch.8
new file mode 100644 (file)
index 0000000..a45aa1d
--- /dev/null
@@ -0,0 +1,59 @@
+.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).
diff --git a/doc/man/innwatch.ctl.5 b/doc/man/innwatch.ctl.5
new file mode 100644 (file)
index 0000000..72290c6
--- /dev/null
@@ -0,0 +1,226 @@
+.\" $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).
diff --git a/doc/man/innxbatch.8 b/doc/man/innxbatch.8
new file mode 100644 (file)
index 0000000..784080d
--- /dev/null
@@ -0,0 +1,126 @@
+.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).
diff --git a/doc/man/innxmit.8 b/doc/man/innxmit.8
new file mode 100644 (file)
index 0000000..a7457b0
--- /dev/null
@@ -0,0 +1,191 @@
+.\" $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).
diff --git a/doc/man/libauth.3 b/doc/man/libauth.3
new file mode 100644 (file)
index 0000000..8289640
--- /dev/null
@@ -0,0 +1,207 @@
+.\" 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
diff --git a/doc/man/libinn.3 b/doc/man/libinn.3
new file mode 100644 (file)
index 0000000..e6fd51f
--- /dev/null
@@ -0,0 +1,533 @@
+.\" $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).
diff --git a/doc/man/libinnhist.3 b/doc/man/libinnhist.3
new file mode 100644 (file)
index 0000000..3f60759
--- /dev/null
@@ -0,0 +1,421 @@
+.\" 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 $
diff --git a/doc/man/libstorage.3 b/doc/man/libstorage.3
new file mode 100644 (file)
index 0000000..c6f2cfd
--- /dev/null
@@ -0,0 +1,405 @@
+.\" $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).
diff --git a/doc/man/list.3 b/doc/man/list.3
new file mode 100644 (file)
index 0000000..6ea53fc
--- /dev/null
@@ -0,0 +1,211 @@
+.\" 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 $
diff --git a/doc/man/mailpost.8 b/doc/man/mailpost.8
new file mode 100644 (file)
index 0000000..a477f70
--- /dev/null
@@ -0,0 +1,258 @@
+.\" 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).
diff --git a/doc/man/makeactive.8 b/doc/man/makeactive.8
new file mode 100644 (file)
index 0000000..7bb63e6
--- /dev/null
@@ -0,0 +1,9 @@
+.\" $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)
diff --git a/doc/man/makedbz.8 b/doc/man/makedbz.8
new file mode 100644 (file)
index 0000000..702d2a7
--- /dev/null
@@ -0,0 +1,78 @@
+.\" $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).
diff --git a/doc/man/makehistory.8 b/doc/man/makehistory.8
new file mode 100644 (file)
index 0000000..5c8582e
--- /dev/null
@@ -0,0 +1,308 @@
+.\" 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).
diff --git a/doc/man/mod-active.8 b/doc/man/mod-active.8
new file mode 100644 (file)
index 0000000..cb224b3
--- /dev/null
@@ -0,0 +1,84 @@
+.\" $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).
diff --git a/doc/man/moderators.5 b/doc/man/moderators.5
new file mode 100644 (file)
index 0000000..35ad6b6
--- /dev/null
@@ -0,0 +1,86 @@
+.\" $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).
diff --git a/doc/man/motd.news.5 b/doc/man/motd.news.5
new file mode 100644 (file)
index 0000000..b59bfac
--- /dev/null
@@ -0,0 +1,154 @@
+.\" 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)
diff --git a/doc/man/news.daily.8 b/doc/man/news.daily.8
new file mode 100644 (file)
index 0000000..feb0a01
--- /dev/null
@@ -0,0 +1,227 @@
+.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).
diff --git a/doc/man/news2mail.8 b/doc/man/news2mail.8
new file mode 100644 (file)
index 0000000..910f1a2
--- /dev/null
@@ -0,0 +1,73 @@
+.\" -*- 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).
+
diff --git a/doc/man/newsfeeds.5 b/doc/man/newsfeeds.5
new file mode 100644 (file)
index 0000000..cc75700
--- /dev/null
@@ -0,0 +1,911 @@
+.\" 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).
diff --git a/doc/man/newslog.5 b/doc/man/newslog.5
new file mode 100644 (file)
index 0000000..ca84f4e
--- /dev/null
@@ -0,0 +1,227 @@
+.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).
diff --git a/doc/man/ninpaths.8 b/doc/man/ninpaths.8
new file mode 100644 (file)
index 0000000..69f20ac
--- /dev/null
@@ -0,0 +1,235 @@
+.\" 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.
diff --git a/doc/man/nnrpd.8 b/doc/man/nnrpd.8
new file mode 100644 (file)
index 0000000..dabf059
--- /dev/null
@@ -0,0 +1,382 @@
+.\" 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).
diff --git a/doc/man/nnrpd.track.5 b/doc/man/nnrpd.track.5
new file mode 100644 (file)
index 0000000..55fdc93
--- /dev/null
@@ -0,0 +1,55 @@
+.\" $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),
diff --git a/doc/man/nntpget.1 b/doc/man/nntpget.1
new file mode 100644 (file)
index 0000000..51fd863
--- /dev/null
@@ -0,0 +1,90 @@
+.\" $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).
diff --git a/doc/man/nntpsend.8 b/doc/man/nntpsend.8
new file mode 100644 (file)
index 0000000..9cee56b
--- /dev/null
@@ -0,0 +1,247 @@
+.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).
diff --git a/doc/man/nntpsend.ctl.5 b/doc/man/nntpsend.ctl.5
new file mode 100644 (file)
index 0000000..0561d07
--- /dev/null
@@ -0,0 +1,46 @@
+.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).
diff --git a/doc/man/ovdb.5 b/doc/man/ovdb.5
new file mode 100644 (file)
index 0000000..528e99e
--- /dev/null
@@ -0,0 +1,412 @@
+.\" 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/>.
diff --git a/doc/man/ovdb_init.8 b/doc/man/ovdb_init.8
new file mode 100644 (file)
index 0000000..92075ea
--- /dev/null
@@ -0,0 +1,234 @@
+.\" 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)
diff --git a/doc/man/ovdb_monitor.8 b/doc/man/ovdb_monitor.8
new file mode 100644 (file)
index 0000000..407ca1b
--- /dev/null
@@ -0,0 +1,155 @@
+.\" 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)
diff --git a/doc/man/ovdb_server.8 b/doc/man/ovdb_server.8
new file mode 100644 (file)
index 0000000..09d3932
--- /dev/null
@@ -0,0 +1,154 @@
+.\" 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)
diff --git a/doc/man/ovdb_stat.8 b/doc/man/ovdb_stat.8
new file mode 100644 (file)
index 0000000..7cb3742
--- /dev/null
@@ -0,0 +1,205 @@
+.\" 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)
diff --git a/doc/man/overchan.8 b/doc/man/overchan.8
new file mode 100644 (file)
index 0000000..02ed021
--- /dev/null
@@ -0,0 +1,60 @@
+.\" $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).
diff --git a/doc/man/overview.fmt.5 b/doc/man/overview.fmt.5
new file mode 100644 (file)
index 0000000..407456a
--- /dev/null
@@ -0,0 +1,58 @@
+.\" $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)
diff --git a/doc/man/parsedate.3 b/doc/man/parsedate.3
new file mode 100644 (file)
index 0000000..4d04b28
--- /dev/null
@@ -0,0 +1,138 @@
+.\" $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).
diff --git a/doc/man/passwd.nntp.5 b/doc/man/passwd.nntp.5
new file mode 100644 (file)
index 0000000..91d4e72
--- /dev/null
@@ -0,0 +1,181 @@
+.\" 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).
diff --git a/doc/man/perl-nocem.8 b/doc/man/perl-nocem.8
new file mode 100644 (file)
index 0000000..cdf7d65
--- /dev/null
@@ -0,0 +1,253 @@
+.\" 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).
diff --git a/doc/man/pgpverify.1 b/doc/man/pgpverify.1
new file mode 100644 (file)
index 0000000..d0d09f4
--- /dev/null
@@ -0,0 +1,293 @@
+.\" 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.
diff --git a/doc/man/prunehistory.8 b/doc/man/prunehistory.8
new file mode 100644 (file)
index 0000000..8cc0c97
--- /dev/null
@@ -0,0 +1,67 @@
+.\" $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).
diff --git a/doc/man/pullnews.1 b/doc/man/pullnews.1
new file mode 100644 (file)
index 0000000..b8936a3
--- /dev/null
@@ -0,0 +1,373 @@
+.\" 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).
diff --git a/doc/man/putman.sh b/doc/man/putman.sh
new file mode 100644 (file)
index 0000000..36a8f19
--- /dev/null
@@ -0,0 +1,51 @@
+#! /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
diff --git a/doc/man/qio.3 b/doc/man/qio.3
new file mode 100644 (file)
index 0000000..4345d79
--- /dev/null
@@ -0,0 +1,242 @@
+.\" 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 $
diff --git a/doc/man/radius.8 b/doc/man/radius.8
new file mode 100644 (file)
index 0000000..ce32e0e
--- /dev/null
@@ -0,0 +1,196 @@
+.\" 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.
diff --git a/doc/man/radius.conf.5 b/doc/man/radius.conf.5
new file mode 100644 (file)
index 0000000..9581607
--- /dev/null
@@ -0,0 +1,226 @@
+.\" 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)
diff --git a/doc/man/rc.news.8 b/doc/man/rc.news.8
new file mode 100644 (file)
index 0000000..a98bb5a
--- /dev/null
@@ -0,0 +1,216 @@
+.\" 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).
diff --git a/doc/man/readers.conf.5 b/doc/man/readers.conf.5
new file mode 100644 (file)
index 0000000..fa8ce69
--- /dev/null
@@ -0,0 +1,932 @@
+.\" 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).
diff --git a/doc/man/rnews.1 b/doc/man/rnews.1
new file mode 100644 (file)
index 0000000..941f1b3
--- /dev/null
@@ -0,0 +1,144 @@
+.\" $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).
diff --git a/doc/man/sasl.conf.5 b/doc/man/sasl.conf.5
new file mode 100644 (file)
index 0000000..27ac6a3
--- /dev/null
@@ -0,0 +1,196 @@
+.\" 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)
diff --git a/doc/man/scanlogs.8 b/doc/man/scanlogs.8
new file mode 100644 (file)
index 0000000..75cc977
--- /dev/null
@@ -0,0 +1,41 @@
+.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).
diff --git a/doc/man/send-nntp.8 b/doc/man/send-nntp.8
new file mode 100644 (file)
index 0000000..2f09242
--- /dev/null
@@ -0,0 +1,84 @@
+.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)
diff --git a/doc/man/send-uucp.8 b/doc/man/send-uucp.8
new file mode 100644 (file)
index 0000000..4b64b98
--- /dev/null
@@ -0,0 +1,235 @@
+.\" 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>.
diff --git a/doc/man/sendinpaths.8 b/doc/man/sendinpaths.8
new file mode 100644 (file)
index 0000000..0bf9493
--- /dev/null
@@ -0,0 +1,163 @@
+.\" 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>.
diff --git a/doc/man/shlock.1 b/doc/man/shlock.1
new file mode 100644 (file)
index 0000000..11bf250
--- /dev/null
@@ -0,0 +1,82 @@
+.\" $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)
diff --git a/doc/man/shrinkfile.1 b/doc/man/shrinkfile.1
new file mode 100644 (file)
index 0000000..8ed35bd
--- /dev/null
@@ -0,0 +1,101 @@
+.\" $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)
diff --git a/doc/man/simpleftp.1 b/doc/man/simpleftp.1
new file mode 100644 (file)
index 0000000..90cf672
--- /dev/null
@@ -0,0 +1,172 @@
+.\" 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).
diff --git a/doc/man/sm.1 b/doc/man/sm.1
new file mode 100644 (file)
index 0000000..88a3685
--- /dev/null
@@ -0,0 +1,211 @@
+.\" 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).
diff --git a/doc/man/startinnfeed.1 b/doc/man/startinnfeed.1
new file mode 100644 (file)
index 0000000..80dfefb
--- /dev/null
@@ -0,0 +1,25 @@
+.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)
diff --git a/doc/man/storage.conf.5 b/doc/man/storage.conf.5
new file mode 100644 (file)
index 0000000..fd47384
--- /dev/null
@@ -0,0 +1,282 @@
+.\" $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).
diff --git a/doc/man/subscriptions.5 b/doc/man/subscriptions.5
new file mode 100644 (file)
index 0000000..c109ed7
--- /dev/null
@@ -0,0 +1,176 @@
+.\" 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).
diff --git a/doc/man/tally.control.8 b/doc/man/tally.control.8
new file mode 100644 (file)
index 0000000..b10e053
--- /dev/null
@@ -0,0 +1,29 @@
+.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).
diff --git a/doc/man/tdx-util.8 b/doc/man/tdx-util.8
new file mode 100644 (file)
index 0000000..b2ab95b
--- /dev/null
@@ -0,0 +1,304 @@
+.\" 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)
diff --git a/doc/man/tst.3 b/doc/man/tst.3
new file mode 100644 (file)
index 0000000..fd9a977
--- /dev/null
@@ -0,0 +1,204 @@
+.\" 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 $
diff --git a/doc/man/uwildmat.3 b/doc/man/uwildmat.3
new file mode 100644 (file)
index 0000000..bbdc88d
--- /dev/null
@@ -0,0 +1,290 @@
+.\" 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).
diff --git a/doc/man/writelog.8 b/doc/man/writelog.8
new file mode 100644 (file)
index 0000000..4d7f706
--- /dev/null
@@ -0,0 +1,38 @@
+.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).
diff --git a/doc/pod/Makefile b/doc/pod/Makefile
new file mode 100644 (file)
index 0000000..2294717
--- /dev/null
@@ -0,0 +1,104 @@
+##  $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 $? > $@
diff --git a/doc/pod/active.pod b/doc/pod/active.pod
new file mode 100644 (file)
index 0000000..a37acd9
--- /dev/null
@@ -0,0 +1,91 @@
+=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
diff --git a/doc/pod/active.times.pod b/doc/pod/active.times.pod
new file mode 100644 (file)
index 0000000..6172913
--- /dev/null
@@ -0,0 +1,35 @@
+=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
diff --git a/doc/pod/auth_krb5.pod b/doc/pod/auth_krb5.pod
new file mode 100644 (file)
index 0000000..adf5f0f
--- /dev/null
@@ -0,0 +1,87 @@
+=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
diff --git a/doc/pod/auth_smb.pod b/doc/pod/auth_smb.pod
new file mode 100644 (file)
index 0000000..f3cda91
--- /dev/null
@@ -0,0 +1,55 @@
+=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
diff --git a/doc/pod/checklist.pod b/doc/pod/checklist.pod
new file mode 100644 (file)
index 0000000..4c8279a
--- /dev/null
@@ -0,0 +1,271 @@
+=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
+
diff --git a/doc/pod/ckpasswd.pod b/doc/pod/ckpasswd.pod
new file mode 100644 (file)
index 0000000..4e5b4d5
--- /dev/null
@@ -0,0 +1,187 @@
+=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
diff --git a/doc/pod/control.ctl.pod b/doc/pod/control.ctl.pod
new file mode 100644 (file)
index 0000000..87876c8
--- /dev/null
@@ -0,0 +1,198 @@
+=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
diff --git a/doc/pod/convdate.pod b/doc/pod/convdate.pod
new file mode 100644 (file)
index 0000000..a025c9e
--- /dev/null
@@ -0,0 +1,106 @@
+=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).
diff --git a/doc/pod/cycbuff.conf.pod b/doc/pod/cycbuff.conf.pod
new file mode 100644 (file)
index 0000000..837b2d2
--- /dev/null
@@ -0,0 +1,201 @@
+=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
diff --git a/doc/pod/distrib.pats.pod b/doc/pod/distrib.pats.pod
new file mode 100644 (file)
index 0000000..5071d8c
--- /dev/null
@@ -0,0 +1,46 @@
+=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
diff --git a/doc/pod/domain.pod b/doc/pod/domain.pod
new file mode 100644 (file)
index 0000000..c98ccef
--- /dev/null
@@ -0,0 +1,60 @@
+=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
diff --git a/doc/pod/expire.ctl.pod b/doc/pod/expire.ctl.pod
new file mode 100644 (file)
index 0000000..bc89c95
--- /dev/null
@@ -0,0 +1,172 @@
+=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
diff --git a/doc/pod/expireover.pod b/doc/pod/expireover.pod
new file mode 100644 (file)
index 0000000..69c27ae
--- /dev/null
@@ -0,0 +1,155 @@
+=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
diff --git a/doc/pod/external-auth.pod b/doc/pod/external-auth.pod
new file mode 100644 (file)
index 0000000..189da34
--- /dev/null
@@ -0,0 +1,112 @@
+=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
diff --git a/doc/pod/fastrm.pod b/doc/pod/fastrm.pod
new file mode 100644 (file)
index 0000000..8f99c58
--- /dev/null
@@ -0,0 +1,190 @@
+=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
diff --git a/doc/pod/grephistory.pod b/doc/pod/grephistory.pod
new file mode 100644 (file)
index 0000000..748e084
--- /dev/null
@@ -0,0 +1,85 @@
+=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
diff --git a/doc/pod/hacking.pod b/doc/pod/hacking.pod
new file mode 100644 (file)
index 0000000..db39c0b
--- /dev/null
@@ -0,0 +1,746 @@
+=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
diff --git a/doc/pod/hook-perl.pod b/doc/pod/hook-perl.pod
new file mode 100644 (file)
index 0000000..a860393
--- /dev/null
@@ -0,0 +1,614 @@
+=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.
diff --git a/doc/pod/hook-python.pod b/doc/pod/hook-python.pod
new file mode 100644 (file)
index 0000000..da59948
--- /dev/null
@@ -0,0 +1,657 @@
+=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
diff --git a/doc/pod/ident.pod b/doc/pod/ident.pod
new file mode 100644 (file)
index 0000000..e9d6250
--- /dev/null
@@ -0,0 +1,70 @@
+=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
diff --git a/doc/pod/inews.pod b/doc/pod/inews.pod
new file mode 100644 (file)
index 0000000..8c8176d
--- /dev/null
@@ -0,0 +1,142 @@
+=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
diff --git a/doc/pod/inn.conf.pod b/doc/pod/inn.conf.pod
new file mode 100644 (file)
index 0000000..3145595
--- /dev/null
@@ -0,0 +1,1273 @@
+=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.
diff --git a/doc/pod/innconfval.pod b/doc/pod/innconfval.pod
new file mode 100644 (file)
index 0000000..c73d6e7
--- /dev/null
@@ -0,0 +1,83 @@
+=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
diff --git a/doc/pod/innd.pod b/doc/pod/innd.pod
new file mode 100644 (file)
index 0000000..f79323c
--- /dev/null
@@ -0,0 +1,513 @@
+=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
diff --git a/doc/pod/inndf.pod b/doc/pod/inndf.pod
new file mode 100644 (file)
index 0000000..f0d1004
--- /dev/null
@@ -0,0 +1,120 @@
+=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
diff --git a/doc/pod/inndstart.pod b/doc/pod/inndstart.pod
new file mode 100644 (file)
index 0000000..84265ea
--- /dev/null
@@ -0,0 +1,317 @@
+=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
diff --git a/doc/pod/innmail.pod b/doc/pod/innmail.pod
new file mode 100644 (file)
index 0000000..a858eec
--- /dev/null
@@ -0,0 +1,68 @@
+=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
diff --git a/doc/pod/innupgrade.pod b/doc/pod/innupgrade.pod
new file mode 100644 (file)
index 0000000..656f05b
--- /dev/null
@@ -0,0 +1,85 @@
+=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
diff --git a/doc/pod/install.pod b/doc/pod/install.pod
new file mode 100644 (file)
index 0000000..3beb412
--- /dev/null
@@ -0,0 +1,1560 @@
+=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.
diff --git a/doc/pod/libauth.pod b/doc/pod/libauth.pod
new file mode 100644 (file)
index 0000000..f31cddf
--- /dev/null
@@ -0,0 +1,73 @@
+=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
diff --git a/doc/pod/libinnhist.pod b/doc/pod/libinnhist.pod
new file mode 100644 (file)
index 0000000..8df364f
--- /dev/null
@@ -0,0 +1,300 @@
+=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 $
diff --git a/doc/pod/list.pod b/doc/pod/list.pod
new file mode 100644 (file)
index 0000000..6b9f631
--- /dev/null
@@ -0,0 +1,83 @@
+=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 $
diff --git a/doc/pod/mailpost.pod b/doc/pod/mailpost.pod
new file mode 100644 (file)
index 0000000..af35829
--- /dev/null
@@ -0,0 +1,152 @@
+=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
+
diff --git a/doc/pod/makehistory.pod b/doc/pod/makehistory.pod
new file mode 100644 (file)
index 0000000..7775fb9
--- /dev/null
@@ -0,0 +1,196 @@
+=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
diff --git a/doc/pod/motd.news.pod b/doc/pod/motd.news.pod
new file mode 100644 (file)
index 0000000..c52fe7a
--- /dev/null
@@ -0,0 +1,26 @@
+=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)
diff --git a/doc/pod/news.pod b/doc/pod/news.pod
new file mode 100644 (file)
index 0000000..39166bf
--- /dev/null
@@ -0,0 +1,1200 @@
+=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
diff --git a/doc/pod/newsfeeds.pod b/doc/pod/newsfeeds.pod
new file mode 100644 (file)
index 0000000..a57ea32
--- /dev/null
@@ -0,0 +1,805 @@
+=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
diff --git a/doc/pod/ninpaths.pod b/doc/pod/ninpaths.pod
new file mode 100644 (file)
index 0000000..98b1c51
--- /dev/null
@@ -0,0 +1,124 @@
+=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
diff --git a/doc/pod/nnrpd.pod b/doc/pod/nnrpd.pod
new file mode 100644 (file)
index 0000000..42080d4
--- /dev/null
@@ -0,0 +1,294 @@
+=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).
diff --git a/doc/pod/ovdb.pod b/doc/pod/ovdb.pod
new file mode 100644 (file)
index 0000000..5f5b38b
--- /dev/null
@@ -0,0 +1,323 @@
+=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
diff --git a/doc/pod/ovdb_init.pod b/doc/pod/ovdb_init.pod
new file mode 100644 (file)
index 0000000..d5c181f
--- /dev/null
@@ -0,0 +1,142 @@
+=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
diff --git a/doc/pod/ovdb_monitor.pod b/doc/pod/ovdb_monitor.pod
new file mode 100644 (file)
index 0000000..14410d5
--- /dev/null
@@ -0,0 +1,30 @@
+=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
diff --git a/doc/pod/ovdb_server.pod b/doc/pod/ovdb_server.pod
new file mode 100644 (file)
index 0000000..f979cc4
--- /dev/null
@@ -0,0 +1,29 @@
+=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
diff --git a/doc/pod/ovdb_stat.pod b/doc/pod/ovdb_stat.pod
new file mode 100644 (file)
index 0000000..fa1d673
--- /dev/null
@@ -0,0 +1,97 @@
+=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
diff --git a/doc/pod/passwd.nntp.pod b/doc/pod/passwd.nntp.pod
new file mode 100644 (file)
index 0000000..d47bd3c
--- /dev/null
@@ -0,0 +1,51 @@
+=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
diff --git a/doc/pod/pullnews.pod b/doc/pod/pullnews.pod
new file mode 100644 (file)
index 0000000..c2b55f0
--- /dev/null
@@ -0,0 +1,284 @@
+=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
diff --git a/doc/pod/qio.pod b/doc/pod/qio.pod
new file mode 100644 (file)
index 0000000..4f46b94
--- /dev/null
@@ -0,0 +1,111 @@
+=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 $
diff --git a/doc/pod/radius.conf.pod b/doc/pod/radius.conf.pod
new file mode 100644 (file)
index 0000000..83dee3e
--- /dev/null
@@ -0,0 +1,109 @@
+=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
diff --git a/doc/pod/radius.pod b/doc/pod/radius.pod
new file mode 100644 (file)
index 0000000..5cf4ec4
--- /dev/null
@@ -0,0 +1,77 @@
+=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
diff --git a/doc/pod/rc.news.pod b/doc/pod/rc.news.pod
new file mode 100644 (file)
index 0000000..bcc076e
--- /dev/null
@@ -0,0 +1,104 @@
+=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
+
diff --git a/doc/pod/readers.conf.pod b/doc/pod/readers.conf.pod
new file mode 100644 (file)
index 0000000..5427e5f
--- /dev/null
@@ -0,0 +1,829 @@
+=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
diff --git a/doc/pod/readme.pod b/doc/pod/readme.pod
new file mode 100644 (file)
index 0000000..2589fe7
--- /dev/null
@@ -0,0 +1,305 @@
+=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>
diff --git a/doc/pod/sasl.conf.pod b/doc/pod/sasl.conf.pod
new file mode 100644 (file)
index 0000000..8c37dba
--- /dev/null
@@ -0,0 +1,78 @@
+=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
diff --git a/doc/pod/sendinpaths.pod b/doc/pod/sendinpaths.pod
new file mode 100644 (file)
index 0000000..98b1ba8
--- /dev/null
@@ -0,0 +1,43 @@
+=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
diff --git a/doc/pod/simpleftp.pod b/doc/pod/simpleftp.pod
new file mode 100644 (file)
index 0000000..0fb4df8
--- /dev/null
@@ -0,0 +1,46 @@
+=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
diff --git a/doc/pod/sm.pod b/doc/pod/sm.pod
new file mode 100644 (file)
index 0000000..68c790e
--- /dev/null
@@ -0,0 +1,95 @@
+=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
diff --git a/doc/pod/subscriptions.pod b/doc/pod/subscriptions.pod
new file mode 100644 (file)
index 0000000..e3d84d8
--- /dev/null
@@ -0,0 +1,49 @@
+=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
diff --git a/doc/pod/tdx-util.pod b/doc/pod/tdx-util.pod
new file mode 100644 (file)
index 0000000..8ea6171
--- /dev/null
@@ -0,0 +1,184 @@
+=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
+
diff --git a/doc/pod/tst.pod b/doc/pod/tst.pod
new file mode 100644 (file)
index 0000000..2b7d8da
--- /dev/null
@@ -0,0 +1,76 @@
+=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 $
diff --git a/doc/pod/uwildmat.pod b/doc/pod/uwildmat.pod
new file mode 100644 (file)
index 0000000..41fffff
--- /dev/null
@@ -0,0 +1,180 @@
+=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
diff --git a/doc/sample-control b/doc/sample-control
new file mode 100644 (file)
index 0000000..5caafb4
--- /dev/null
@@ -0,0 +1,26 @@
+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)
diff --git a/expire/Makefile b/expire/Makefile
new file mode 100644 (file)
index 0000000..a2cb05d
--- /dev/null
@@ -0,0 +1,131 @@
+##  $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
diff --git a/expire/convdate.c b/expire/convdate.c
new file mode 100644 (file)
index 0000000..d508bb8
--- /dev/null
@@ -0,0 +1,167 @@
+/*  $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);
+    }
+}
diff --git a/expire/expire.c b/expire/expire.c
new file mode 100644 (file)
index 0000000..108ce1f
--- /dev/null
@@ -0,0 +1,727 @@
+/*  $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();
+}
diff --git a/expire/expireover.c b/expire/expireover.c
new file mode 100644 (file)
index 0000000..57e0cdb
--- /dev/null
@@ -0,0 +1,244 @@
+/*  $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;
+}
diff --git a/expire/expirerm.in b/expire/expirerm.in
new file mode 100644 (file)
index 0000000..4757ef2
--- /dev/null
@@ -0,0 +1,30 @@
+#! /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
diff --git a/expire/fastrm.c b/expire/fastrm.c
new file mode 100644 (file)
index 0000000..bd2144a
--- /dev/null
@@ -0,0 +1,728 @@
+/*  $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);
+}
diff --git a/expire/grephistory.c b/expire/grephistory.c
new file mode 100644 (file)
index 0000000..ed6135d
--- /dev/null
@@ -0,0 +1,180 @@
+/*  $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;
+}
diff --git a/expire/makedbz.c b/expire/makedbz.c
new file mode 100644 (file)
index 0000000..bc1fe7d
--- /dev/null
@@ -0,0 +1,318 @@
+/*  $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);
+}
diff --git a/expire/makehistory.c b/expire/makehistory.c
new file mode 100644 (file)
index 0000000..7152b99
--- /dev/null
@@ -0,0 +1,944 @@
+/*  $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);
+}
+
diff --git a/expire/prunehistory.c b/expire/prunehistory.c
new file mode 100644 (file)
index 0000000..cb30c75
--- /dev/null
@@ -0,0 +1,125 @@
+/*  $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;
+}
diff --git a/extra/active b/extra/active
new file mode 100644 (file)
index 0000000..f98b10a
--- /dev/null
@@ -0,0 +1,8 @@
+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
diff --git a/extra/buildinnkeyring b/extra/buildinnkeyring
new file mode 100644 (file)
index 0000000..7d2c247
--- /dev/null
@@ -0,0 +1,36 @@
+#!/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
+
diff --git a/extra/bunbatch b/extra/bunbatch
new file mode 100644 (file)
index 0000000..ba380ff
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /bin/bzip2 -d -c
diff --git a/extra/dh_cloneconf b/extra/dh_cloneconf
new file mode 100644 (file)
index 0000000..d04d1c6
--- /dev/null
@@ -0,0 +1,15 @@
+#!/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
+
diff --git a/extra/ginpaths2 b/extra/ginpaths2
new file mode 100644 (file)
index 0000000..ba021d3
--- /dev/null
@@ -0,0 +1,6 @@
+#!/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
diff --git a/extra/newsgroups b/extra/newsgroups
new file mode 100644 (file)
index 0000000..9d2ed2a
--- /dev/null
@@ -0,0 +1,8 @@
+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
diff --git a/extra/sasl.conf b/extra/sasl.conf
new file mode 100644 (file)
index 0000000..94d894d
--- /dev/null
@@ -0,0 +1,4 @@
+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
diff --git a/extra/send-uucp.cf b/extra/send-uucp.cf
new file mode 100644 (file)
index 0000000..ea7ac95
--- /dev/null
@@ -0,0 +1,26 @@
+#
+# 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
diff --git a/frontends/Makefile b/frontends/Makefile
new file mode 100644 (file)
index 0000000..0e44f2d
--- /dev/null
@@ -0,0 +1,185 @@
+##  $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
diff --git a/frontends/cnfsheadconf.in b/frontends/cnfsheadconf.in
new file mode 100644 (file)
index 0000000..a87ebe1
--- /dev/null
@@ -0,0 +1,342 @@
+#! /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);
+}
diff --git a/frontends/cnfsstat.in b/frontends/cnfsstat.in
new file mode 100644 (file)
index 0000000..dd449b9
--- /dev/null
@@ -0,0 +1,552 @@
+#! /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;
+}
diff --git a/frontends/ctlinnd.c b/frontends/ctlinnd.c
new file mode 100644 (file)
index 0000000..e2d8ada
--- /dev/null
@@ -0,0 +1,342 @@
+/*  $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 */
+}
diff --git a/frontends/decode.c b/frontends/decode.c
new file mode 100644 (file)
index 0000000..a862ccf
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+**  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 */
+}
diff --git a/frontends/encode.c b/frontends/encode.c
new file mode 100644 (file)
index 0000000..f23b882
--- /dev/null
@@ -0,0 +1,116 @@
+/*  $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 */
+}
diff --git a/frontends/feedone.c b/frontends/feedone.c
new file mode 100644 (file)
index 0000000..21dc78a
--- /dev/null
@@ -0,0 +1,192 @@
+/*  $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 */
+}
diff --git a/frontends/getlist.c b/frontends/getlist.c
new file mode 100644 (file)
index 0000000..3e770d7
--- /dev/null
@@ -0,0 +1,194 @@
+/*  $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 */
+}
diff --git a/frontends/inews.c b/frontends/inews.c
new file mode 100644 (file)
index 0000000..ff57635
--- /dev/null
@@ -0,0 +1,1092 @@
+/*  $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;
+}
diff --git a/frontends/innconfval.c b/frontends/innconfval.c
new file mode 100644 (file)
index 0000000..2f830c5
--- /dev/null
@@ -0,0 +1,102 @@
+/*  $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);
+}
diff --git a/frontends/mailpost.in b/frontends/mailpost.in
new file mode 100644 (file)
index 0000000..5d1695b
--- /dev/null
@@ -0,0 +1,565 @@
+#! /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 ;
+
+}
+
diff --git a/frontends/ovdb_init.c b/frontends/ovdb_init.c
new file mode 100644 (file)
index 0000000..b87ec68
--- /dev/null
@@ -0,0 +1,481 @@
+/*
+ * 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 */
+
diff --git a/frontends/ovdb_monitor.c b/frontends/ovdb_monitor.c
new file mode 100644 (file)
index 0000000..3fbd9b7
--- /dev/null
@@ -0,0 +1,331 @@
+/*
+ * 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 */
+
diff --git a/frontends/ovdb_server.c b/frontends/ovdb_server.c
new file mode 100644 (file)
index 0000000..af6d7cb
--- /dev/null
@@ -0,0 +1,744 @@
+/*
+ * 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 */
diff --git a/frontends/ovdb_stat.c b/frontends/ovdb_stat.c
new file mode 100644 (file)
index 0000000..1166237
--- /dev/null
@@ -0,0 +1,1031 @@
+/*
+ * 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) ? "&nbsp;":"");
+                   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>&nbsp;<td>&nbsp;");
+                   if(gi.expired)
+                       printf("<td>%s<td>%lu", myctime(&gi.expired),
+                               (unsigned long) gi.expiregrouppid);
+                   else
+                       printf("<td>&nbsp;<td>&nbsp;");
+                   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 */
+
diff --git a/frontends/pullnews.in b/frontends/pullnews.in
new file mode 100644 (file)
index 0000000..5e54080
--- /dev/null
@@ -0,0 +1,822 @@
+#! /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;
+}
diff --git a/frontends/rnews.c b/frontends/rnews.c
new file mode 100644 (file)
index 0000000..5ef5f3b
--- /dev/null
@@ -0,0 +1,972 @@
+/*  $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 */
+}
diff --git a/frontends/scanspool.in b/frontends/scanspool.in
new file mode 100644 (file)
index 0000000..1381c9c
--- /dev/null
@@ -0,0 +1,460 @@
+#! /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);
+}
diff --git a/frontends/sm.c b/frontends/sm.c
new file mode 100644 (file)
index 0000000..a3017c4
--- /dev/null
@@ -0,0 +1,189 @@
+/*  $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);
+}
diff --git a/frontends/sys2nf.c b/frontends/sys2nf.c
new file mode 100644 (file)
index 0000000..2d949a9
--- /dev/null
@@ -0,0 +1,343 @@
+/*  $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 */
+}
diff --git a/history/Make.methods b/history/Make.methods
new file mode 100644 (file)
index 0000000..f422c81
--- /dev/null
@@ -0,0 +1,5 @@
+# This file is automatically generated by buildconfig
+
+METHOD_SOURCES  = hisv6/hisv6.c
+EXTRA_SOURCES   =
+PROGRAMS        =
diff --git a/history/Makefile b/history/Makefile
new file mode 100644 (file)
index 0000000..404cc24
--- /dev/null
@@ -0,0 +1,108 @@
+##  $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
diff --git a/history/buildconfig.in b/history/buildconfig.in
new file mode 100644 (file)
index 0000000..24ae7d5
--- /dev/null
@@ -0,0 +1,197 @@
+#! /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);
diff --git a/history/his.c b/history/his.c
new file mode 100644 (file)
index 0000000..aafd14f
--- /dev/null
@@ -0,0 +1,473 @@
+/*  $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;
+    }
+}
diff --git a/history/hisinterface.h b/history/hisinterface.h
new file mode 100644 (file)
index 0000000..8fa5390
--- /dev/null
@@ -0,0 +1,47 @@
+/* $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
diff --git a/history/hisv6/hismethod.config b/history/hisv6/hismethod.config
new file mode 100644 (file)
index 0000000..4182ab0
--- /dev/null
@@ -0,0 +1,3 @@
+name    = hisv6
+number  = 0
+sources = hisv6.c
diff --git a/history/hisv6/hisv6-private.h b/history/hisv6/hisv6-private.h
new file mode 100644 (file)
index 0000000..51398ea
--- /dev/null
@@ -0,0 +1,81 @@
+#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
diff --git a/history/hisv6/hisv6.c b/history/hisv6/hisv6.c
new file mode 100644 (file)
index 0000000..edcb965
--- /dev/null
@@ -0,0 +1,1426 @@
+/*  $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 = &ltoken;
+           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;
+}
diff --git a/history/hisv6/hisv6.h b/history/hisv6/hisv6.h
new file mode 100644 (file)
index 0000000..6c132bf
--- /dev/null
@@ -0,0 +1,45 @@
+/* $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
diff --git a/include/Makefile b/include/Makefile
new file mode 100644 (file)
index 0000000..fed5549
--- /dev/null
@@ -0,0 +1,53 @@
+##  $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
diff --git a/include/acconfig.h b/include/acconfig.h
new file mode 100644 (file)
index 0000000..1b9cbf1
--- /dev/null
@@ -0,0 +1,262 @@
+/*  $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 */
diff --git a/include/clibrary.h b/include/clibrary.h
new file mode 100644 (file)
index 0000000..d57c6d1
--- /dev/null
@@ -0,0 +1,186 @@
+/*  $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 */
diff --git a/include/conffile.h b/include/conffile.h
new file mode 100644 (file)
index 0000000..78bc972
--- /dev/null
@@ -0,0 +1,35 @@
+/*  $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
diff --git a/include/config.h.in b/include/config.h.in
new file mode 100644 (file)
index 0000000..47e6a39
--- /dev/null
@@ -0,0 +1,674 @@
+/* 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 */
diff --git a/include/dbz.h b/include/dbz.h
new file mode 100644 (file)
index 0000000..f8390b6
--- /dev/null
@@ -0,0 +1,76 @@
+#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__ */
diff --git a/include/inn/buffer.h b/include/inn/buffer.h
new file mode 100644 (file)
index 0000000..e9deb2b
--- /dev/null
@@ -0,0 +1,48 @@
+/*  $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 */
diff --git a/include/inn/confparse.h b/include/inn/confparse.h
new file mode 100644 (file)
index 0000000..5c2aa1c
--- /dev/null
@@ -0,0 +1,78 @@
+/*  $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 */
diff --git a/include/inn/defines.h b/include/inn/defines.h
new file mode 100644 (file)
index 0000000..74f4969
--- /dev/null
@@ -0,0 +1,66 @@
+/*  $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 */
diff --git a/include/inn/hashtab.h b/include/inn/hashtab.h
new file mode 100644 (file)
index 0000000..8d9f31e
--- /dev/null
@@ -0,0 +1,60 @@
+/*  $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 */
diff --git a/include/inn/history.h b/include/inn/history.h
new file mode 100644 (file)
index 0000000..48c6e25
--- /dev/null
@@ -0,0 +1,110 @@
+/*  $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
diff --git a/include/inn/innconf.h b/include/inn/innconf.h
new file mode 100644 (file)
index 0000000..8cd24d2
--- /dev/null
@@ -0,0 +1,211 @@
+/*  $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 */
diff --git a/include/inn/list.h b/include/inn/list.h
new file mode 100644 (file)
index 0000000..99004e4
--- /dev/null
@@ -0,0 +1,51 @@
+/*  $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 */
diff --git a/include/inn/md5.h b/include/inn/md5.h
new file mode 100644 (file)
index 0000000..f0b1584
--- /dev/null
@@ -0,0 +1,79 @@
+/*  $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 */
diff --git a/include/inn/messages.h b/include/inn/messages.h
new file mode 100644 (file)
index 0000000..22297fa
--- /dev/null
@@ -0,0 +1,99 @@
+/*  $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 */
diff --git a/include/inn/mmap.h b/include/inn/mmap.h
new file mode 100644 (file)
index 0000000..3769d51
--- /dev/null
@@ -0,0 +1,33 @@
+/*  $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 */
diff --git a/include/inn/qio.h b/include/inn/qio.h
new file mode 100644 (file)
index 0000000..132da6f
--- /dev/null
@@ -0,0 +1,49 @@
+/*  $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 */
diff --git a/include/inn/sequence.h b/include/inn/sequence.h
new file mode 100644 (file)
index 0000000..d7bfa33
--- /dev/null
@@ -0,0 +1,21 @@
+/*  $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 */
diff --git a/include/inn/timer.h b/include/inn/timer.h
new file mode 100644 (file)
index 0000000..0cc611f
--- /dev/null
@@ -0,0 +1,38 @@
+/*  $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 */
diff --git a/include/inn/tst.h b/include/inn/tst.h
new file mode 100644 (file)
index 0000000..44bfff6
--- /dev/null
@@ -0,0 +1,88 @@
+/*  $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 */
diff --git a/include/inn/vector.h b/include/inn/vector.h
new file mode 100644 (file)
index 0000000..ed63e32
--- /dev/null
@@ -0,0 +1,87 @@
+/*  $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 */
diff --git a/include/inn/wire.h b/include/inn/wire.h
new file mode 100644 (file)
index 0000000..bafae0b
--- /dev/null
@@ -0,0 +1,46 @@
+/*  $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 */
diff --git a/include/inndcomm.h b/include/inndcomm.h
new file mode 100644 (file)
index 0000000..98e63d6
--- /dev/null
@@ -0,0 +1,92 @@
+/*  $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 */
diff --git a/include/innperl.h b/include/innperl.h
new file mode 100644 (file)
index 0000000..84ff112
--- /dev/null
@@ -0,0 +1,27 @@
+/*  $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 */
diff --git a/include/libinn.h b/include/libinn.h
new file mode 100644 (file)
index 0000000..166372b
--- /dev/null
@@ -0,0 +1,214 @@
+/*  $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 */
diff --git a/include/nntp.h b/include/nntp.h
new file mode 100644 (file)
index 0000000..158a941
--- /dev/null
@@ -0,0 +1,175 @@
+/*  $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
diff --git a/include/ov.h b/include/ov.h
new file mode 100644 (file)
index 0000000..5e9b935
--- /dev/null
@@ -0,0 +1,80 @@
+#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_ */
diff --git a/include/paths.h.in b/include/paths.h.in
new file mode 100644 (file)
index 0000000..27e78c3
--- /dev/null
@@ -0,0 +1,113 @@
+/*  $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"
diff --git a/include/portable/mmap.h b/include/portable/mmap.h
new file mode 100644 (file)
index 0000000..3d0bbe2
--- /dev/null
@@ -0,0 +1,66 @@
+/*  $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 */
diff --git a/include/portable/setproctitle.h b/include/portable/setproctitle.h
new file mode 100644 (file)
index 0000000..e15e591
--- /dev/null
@@ -0,0 +1,25 @@
+/*  $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 */
diff --git a/include/portable/socket.h b/include/portable/socket.h
new file mode 100644 (file)
index 0000000..804a203
--- /dev/null
@@ -0,0 +1,84 @@
+/*  $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 */
diff --git a/include/portable/time.h b/include/portable/time.h
new file mode 100644 (file)
index 0000000..42d4961
--- /dev/null
@@ -0,0 +1,25 @@
+/*  $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 */
diff --git a/include/portable/wait.h b/include/portable/wait.h
new file mode 100644 (file)
index 0000000..b6f5109
--- /dev/null
@@ -0,0 +1,41 @@
+/*  $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 */
diff --git a/include/ppport.h b/include/ppport.h
new file mode 100644 (file)
index 0000000..d7d1fb6
--- /dev/null
@@ -0,0 +1,195 @@
+
+#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 */
diff --git a/include/storage.h b/include/storage.h
new file mode 100644 (file)
index 0000000..9612a28
--- /dev/null
@@ -0,0 +1,97 @@
+/*  $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
diff --git a/innd/Makefile b/innd/Makefile
new file mode 100644 (file)
index 0000000..61ebcae
--- /dev/null
@@ -0,0 +1,245 @@
+##  $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
diff --git a/innd/art.c b/innd/art.c
new file mode 100644 (file)
index 0000000..66f212b
--- /dev/null
@@ -0,0 +1,2545 @@
+/*  $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;
+}
diff --git a/innd/cc.c b/innd/cc.c
new file mode 100644 (file)
index 0000000..be17183
--- /dev/null
+++ b/innd/cc.c
@@ -0,0 +1,2107 @@
+/*  $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;
+}
diff --git a/innd/chan.c b/innd/chan.c
new file mode 100644 (file)
index 0000000..664c943
--- /dev/null
@@ -0,0 +1,1235 @@
+/*  $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);
+    }
+}
diff --git a/innd/icd.c b/innd/icd.c
new file mode 100644 (file)
index 0000000..4066197
--- /dev/null
@@ -0,0 +1,510 @@
+/*  $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 */
+}
diff --git a/innd/innd.c b/innd/innd.c
new file mode 100644 (file)
index 0000000..8b1589f
--- /dev/null
@@ -0,0 +1,735 @@
+/*  $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;
+}
diff --git a/innd/innd.h b/innd/innd.h
new file mode 100644 (file)
index 0000000..d8b60f0
--- /dev/null
@@ -0,0 +1,858 @@
+/*  $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 */
diff --git a/innd/inndstart.c b/innd/inndstart.c
new file mode 100644 (file)
index 0000000..56ac6e6
--- /dev/null
@@ -0,0 +1,416 @@
+/*  $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;
+}
diff --git a/innd/keywords.c b/innd/keywords.c
new file mode 100644 (file)
index 0000000..c4340e8
--- /dev/null
@@ -0,0 +1,271 @@
+/*  $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 */
diff --git a/innd/lc.c b/innd/lc.c
new file mode 100644 (file)
index 0000000..4e16daf
--- /dev/null
+++ b/innd/lc.c
@@ -0,0 +1,119 @@
+/*  $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) */
+}
diff --git a/innd/nc.c b/innd/nc.c
new file mode 100644 (file)
index 0000000..c27f45f
--- /dev/null
+++ b/innd/nc.c
@@ -0,0 +1,1462 @@
+/*  $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);
+    }
+}
diff --git a/innd/newsfeeds.c b/innd/newsfeeds.c
new file mode 100644 (file)
index 0000000..a5ac9da
--- /dev/null
@@ -0,0 +1,957 @@
+/*  $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);
+  }
+}
diff --git a/innd/ng.c b/innd/ng.c
new file mode 100644 (file)
index 0000000..5f83f3b
--- /dev/null
+++ b/innd/ng.c
@@ -0,0 +1,425 @@
+/*  $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;
+}
diff --git a/innd/perl.c b/innd/perl.c
new file mode 100644 (file)
index 0000000..a994242
--- /dev/null
@@ -0,0 +1,502 @@
+/*  $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) */
diff --git a/innd/proc.c b/innd/proc.c
new file mode 100644 (file)
index 0000000..c3b55dc
--- /dev/null
@@ -0,0 +1,181 @@
+/*  $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);
+}
diff --git a/innd/python.c b/innd/python.c
new file mode 100644 (file)
index 0000000..f14a312
--- /dev/null
@@ -0,0 +1,732 @@
+/*  $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) */
diff --git a/innd/rc.c b/innd/rc.c
new file mode 100644 (file)
index 0000000..356fed7
--- /dev/null
+++ b/innd/rc.c
@@ -0,0 +1,2017 @@
+/*  $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;
+    }
+}
diff --git a/innd/site.c b/innd/site.c
new file mode 100644 (file)
index 0000000..09c405e
--- /dev/null
@@ -0,0 +1,1246 @@
+/*  $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);
+}
diff --git a/innd/status.c b/innd/status.c
new file mode 100644 (file)
index 0000000..f3c2d8d
--- /dev/null
@@ -0,0 +1,359 @@
+/*  $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;
+  }
+}
diff --git a/innd/tcl.c b/innd/tcl.c
new file mode 100644 (file)
index 0000000..d1c8163
--- /dev/null
@@ -0,0 +1,196 @@
+/*  $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) */
diff --git a/innd/util.c b/innd/util.c
new file mode 100644 (file)
index 0000000..e0b3b3f
--- /dev/null
@@ -0,0 +1,382 @@
+/*  $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);
+}
+
+
diff --git a/innd/wip.c b/innd/wip.c
new file mode 100644 (file)
index 0000000..7e8c8c4
--- /dev/null
@@ -0,0 +1,187 @@
+/* $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;
+}
diff --git a/innfeed/Makefile b/innfeed/Makefile
new file mode 100644 (file)
index 0000000..032521f
--- /dev/null
@@ -0,0 +1,176 @@
+##  $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
diff --git a/innfeed/README b/innfeed/README
new file mode 100644 (file)
index 0000000..ecdefbb
--- /dev/null
@@ -0,0 +1,412 @@
+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.
diff --git a/innfeed/article.c b/innfeed/article.c
new file mode 100644 (file)
index 0000000..9896362
--- /dev/null
@@ -0,0 +1,1061 @@
+/*  $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
+}
diff --git a/innfeed/article.h b/innfeed/article.h
new file mode 100644 (file)
index 0000000..ece277d
--- /dev/null
@@ -0,0 +1,80 @@
+/*  $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__ */
diff --git a/innfeed/buffer.c b/innfeed/buffer.c
new file mode 100644 (file)
index 0000000..25a3478
--- /dev/null
@@ -0,0 +1,550 @@
+/*  $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 ;
+}
diff --git a/innfeed/buffer.h b/innfeed/buffer.h
new file mode 100644 (file)
index 0000000..9e9ed96
--- /dev/null
@@ -0,0 +1,119 @@
+/*  $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__ */
diff --git a/innfeed/config_l.c b/innfeed/config_l.c
new file mode 100644 (file)
index 0000000..34d4c00
--- /dev/null
@@ -0,0 +1,1861 @@
+/* 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"
+
+
+
+
diff --git a/innfeed/configfile.h b/innfeed/configfile.h
new file mode 100644 (file)
index 0000000..7d97266
--- /dev/null
@@ -0,0 +1,106 @@
+/*  $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__ */
diff --git a/innfeed/configfile.l b/innfeed/configfile.l
new file mode 100644 (file)
index 0000000..07fee4c
--- /dev/null
@@ -0,0 +1,252 @@
+%{
+/*  $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) ;
+}
+
+%%
+
+
+
diff --git a/innfeed/configfile.y b/innfeed/configfile.y
new file mode 100644 (file)
index 0000000..007bf0e
--- /dev/null
@@ -0,0 +1,1103 @@
+%{
+/*  $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) */
diff --git a/innfeed/connection.c b/innfeed/connection.c
new file mode 100644 (file)
index 0000000..59d95cc
--- /dev/null
@@ -0,0 +1,4904 @@
+/*  $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 ;
+}
diff --git a/innfeed/connection.h b/innfeed/connection.h
new file mode 100644 (file)
index 0000000..f75a60e
--- /dev/null
@@ -0,0 +1,116 @@
+/*  $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__ */
diff --git a/innfeed/endpoint.c b/innfeed/endpoint.c
new file mode 100644 (file)
index 0000000..d03254a
--- /dev/null
@@ -0,0 +1,1803 @@
+/*  $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 ;
+}
diff --git a/innfeed/endpoint.h b/innfeed/endpoint.h
new file mode 100644 (file)
index 0000000..9690183
--- /dev/null
@@ -0,0 +1,184 @@
+/*  $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__ */
diff --git a/innfeed/host.c b/innfeed/host.c
new file mode 100644 (file)
index 0000000..aa4259c
--- /dev/null
@@ -0,0 +1,4046 @@
+/*  $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 ;
+}
diff --git a/innfeed/host.h b/innfeed/host.h
new file mode 100644 (file)
index 0000000..d773895
--- /dev/null
@@ -0,0 +1,207 @@
+/*  $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__ */
diff --git a/innfeed/imap_connection.c b/innfeed/imap_connection.c
new file mode 100644 (file)
index 0000000..f575686
--- /dev/null
@@ -0,0 +1,4681 @@
+/*  $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;
+}
diff --git a/innfeed/innfeed-convcfg.in b/innfeed/innfeed-convcfg.in
new file mode 100644 (file)
index 0000000..d23d18a
--- /dev/null
@@ -0,0 +1,187 @@
+#! /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" ;
+    }
+}
+       
+       
diff --git a/innfeed/innfeed.h b/innfeed/innfeed.h
new file mode 100644 (file)
index 0000000..e743174
--- /dev/null
@@ -0,0 +1,203 @@
+/*  $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__ */
diff --git a/innfeed/innlistener.c b/innfeed/innlistener.c
new file mode 100644 (file)
index 0000000..c684dde
--- /dev/null
@@ -0,0 +1,784 @@
+/*  $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 ;
+}
diff --git a/innfeed/innlistener.h b/innfeed/innlistener.h
new file mode 100644 (file)
index 0000000..1c7c1e0
--- /dev/null
@@ -0,0 +1,71 @@
+/*  $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__ */
diff --git a/innfeed/main.c b/innfeed/main.c
new file mode 100644 (file)
index 0000000..9ff6537
--- /dev/null
@@ -0,0 +1,967 @@
+/*  $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") ;
+}
diff --git a/innfeed/misc.c b/innfeed/misc.c
new file mode 100644 (file)
index 0000000..0da269d
--- /dev/null
@@ -0,0 +1,782 @@
+/*  $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 ;
+}
diff --git a/innfeed/misc.h b/innfeed/misc.h
new file mode 100644 (file)
index 0000000..4036b13
--- /dev/null
@@ -0,0 +1,139 @@
+/*  $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__ */
diff --git a/innfeed/procbatch.in b/innfeed/procbatch.in
new file mode 100644 (file)
index 0000000..7a203af
--- /dev/null
@@ -0,0 +1,146 @@
+#! /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 ) ;
diff --git a/innfeed/startinnfeed.c b/innfeed/startinnfeed.c
new file mode 100644 (file)
index 0000000..f8523cb
--- /dev/null
@@ -0,0 +1,130 @@
+/*  $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;
+}
diff --git a/innfeed/tape.c b/innfeed/tape.c
new file mode 100644 (file)
index 0000000..a37f5c6
--- /dev/null
@@ -0,0 +1,1294 @@
+/*  $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 ;
+}
diff --git a/innfeed/tape.h b/innfeed/tape.h
new file mode 100644 (file)
index 0000000..2c53772
--- /dev/null
@@ -0,0 +1,68 @@
+/*  $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__ */
diff --git a/innfeed/testListener.pl b/innfeed/testListener.pl
new file mode 100644 (file)
index 0000000..0265506
--- /dev/null
@@ -0,0 +1,160 @@
+#!/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 ;
diff --git a/lib/Makefile b/lib/Makefile
new file mode 100644 (file)
index 0000000..54210bb
--- /dev/null
@@ -0,0 +1,298 @@
+##  $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
diff --git a/lib/buffer.c b/lib/buffer.c
new file mode 100644 (file)
index 0000000..dc3c0a3
--- /dev/null
@@ -0,0 +1,99 @@
+/*  $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;
+}
diff --git a/lib/cleanfrom.c b/lib/cleanfrom.c
new file mode 100644 (file)
index 0000000..94e6de5
--- /dev/null
@@ -0,0 +1,83 @@
+/*  $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';
+}
diff --git a/lib/clientactive.c b/lib/clientactive.c
new file mode 100644 (file)
index 0000000..8e7e741
--- /dev/null
@@ -0,0 +1,144 @@
+/*  $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;
+    }
+}
diff --git a/lib/clientlib.c b/lib/clientlib.c
new file mode 100644 (file)
index 0000000..b0d0268
--- /dev/null
@@ -0,0 +1,157 @@
+/*  $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;
+    }
+}
diff --git a/lib/concat.c b/lib/concat.c
new file mode 100644 (file)
index 0000000..97c7909
--- /dev/null
@@ -0,0 +1,79 @@
+/*  $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);
+}
diff --git a/lib/conffile.c b/lib/conffile.c
new file mode 100644 (file)
index 0000000..359a7e5
--- /dev/null
@@ -0,0 +1,170 @@
+/*  $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);
+}
diff --git a/lib/confparse.c b/lib/confparse.c
new file mode 100644 (file)
index 0000000..c12b1ef
--- /dev/null
@@ -0,0 +1,1301 @@
+/*  $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,
+                   ...)
+{
+}
diff --git a/lib/daemonize.c b/lib/daemonize.c
new file mode 100644 (file)
index 0000000..689bccd
--- /dev/null
@@ -0,0 +1,64 @@
+/*  $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);
+    }
+}
diff --git a/lib/date.c b/lib/date.c
new file mode 100644 (file)
index 0000000..3296b3a
--- /dev/null
@@ -0,0 +1,637 @@
+/*  $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;
+}
diff --git a/lib/dbz.c b/lib/dbz.c
new file mode 100644 (file)
index 0000000..6671df1
--- /dev/null
+++ b/lib/dbz.c
@@ -0,0 +1,1785 @@
+/*
+  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 */
diff --git a/lib/defdist.c b/lib/defdist.c
new file mode 100644 (file)
index 0000000..715a52f
--- /dev/null
@@ -0,0 +1,191 @@
+/*  $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) */
diff --git a/lib/fdflags.c b/lib/fdflags.c
new file mode 100644 (file)
index 0000000..55b3ceb
--- /dev/null
@@ -0,0 +1,103 @@
+/*  $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 */
diff --git a/lib/fdlimit.c b/lib/fdlimit.c
new file mode 100644 (file)
index 0000000..23cf19e
--- /dev/null
@@ -0,0 +1,136 @@
+/*  $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
diff --git a/lib/fseeko.c b/lib/fseeko.c
new file mode 100644 (file)
index 0000000..01d1d26
--- /dev/null
@@ -0,0 +1,56 @@
+/*  $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 */
diff --git a/lib/ftello.c b/lib/ftello.c
new file mode 100644 (file)
index 0000000..a3fcf62
--- /dev/null
@@ -0,0 +1,38 @@
+/*  $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 */
diff --git a/lib/genid.c b/lib/genid.c
new file mode 100644 (file)
index 0000000..d9fa156
--- /dev/null
@@ -0,0 +1,39 @@
+/*  $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;
+}
diff --git a/lib/getfqdn.c b/lib/getfqdn.c
new file mode 100644 (file)
index 0000000..62781c1
--- /dev/null
@@ -0,0 +1,87 @@
+/*  $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;
+}
diff --git a/lib/getmodaddr.c b/lib/getmodaddr.c
new file mode 100644 (file)
index 0000000..05b1c44
--- /dev/null
@@ -0,0 +1,177 @@
+/*  $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;
+}
diff --git a/lib/getpagesize.c b/lib/getpagesize.c
new file mode 100644 (file)
index 0000000..62703ba
--- /dev/null
@@ -0,0 +1,24 @@
+/*  $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;
+}
diff --git a/lib/gettime.c b/lib/gettime.c
new file mode 100644 (file)
index 0000000..01703bb
--- /dev/null
@@ -0,0 +1,80 @@
+/*  $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;
+}
diff --git a/lib/hash.c b/lib/hash.c
new file mode 100644 (file)
index 0000000..2a7cbf3
--- /dev/null
@@ -0,0 +1,163 @@
+/* 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));
+}
diff --git a/lib/hashtab.c b/lib/hashtab.c
new file mode 100644 (file)
index 0000000..ce0fa74
--- /dev/null
@@ -0,0 +1,457 @@
+/*  $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);
+}
diff --git a/lib/hstrerror.c b/lib/hstrerror.c
new file mode 100644 (file)
index 0000000..3729560
--- /dev/null
@@ -0,0 +1,44 @@
+/*  $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;
+}
diff --git a/lib/inet_aton.c b/lib/inet_aton.c
new file mode 100644 (file)
index 0000000..bed9fa4
--- /dev/null
@@ -0,0 +1,130 @@
+/*  $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;
+}
diff --git a/lib/inet_ntoa.c b/lib/inet_ntoa.c
new file mode 100644 (file)
index 0000000..55a22d4
--- /dev/null
@@ -0,0 +1,37 @@
+/*  $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;
+}
diff --git a/lib/innconf.c b/lib/innconf.c
new file mode 100644 (file)
index 0000000..f04510e
--- /dev/null
@@ -0,0 +1,785 @@
+/*  $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;
+}
diff --git a/lib/inndcomm.c b/lib/inndcomm.c
new file mode 100644 (file)
index 0000000..28d5366
--- /dev/null
@@ -0,0 +1,469 @@
+/*  $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);
+}
diff --git a/lib/list.c b/lib/list.c
new file mode 100644 (file)
index 0000000..a127abc
--- /dev/null
@@ -0,0 +1,125 @@
+/*  $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;
+}
diff --git a/lib/localopen.c b/lib/localopen.c
new file mode 100644 (file)
index 0000000..8460e32
--- /dev/null
@@ -0,0 +1,89 @@
+/*  $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) */
+}
diff --git a/lib/lockfile.c b/lib/lockfile.c
new file mode 100644 (file)
index 0000000..185b3e1
--- /dev/null
@@ -0,0 +1,45 @@
+/*  $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);
+}
diff --git a/lib/makedir.c b/lib/makedir.c
new file mode 100644 (file)
index 0000000..737aeb5
--- /dev/null
@@ -0,0 +1,55 @@
+#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);
+}
diff --git a/lib/md5.c b/lib/md5.c
new file mode 100644 (file)
index 0000000..6abbee9
--- /dev/null
+++ b/lib/md5.c
@@ -0,0 +1,513 @@
+/*  $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;
+}
diff --git a/lib/memcmp.c b/lib/memcmp.c
new file mode 100644 (file)
index 0000000..b89b875
--- /dev/null
@@ -0,0 +1,42 @@
+/*  $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;
+}
diff --git a/lib/messages.c b/lib/messages.c
new file mode 100644 (file)
index 0000000..62e6f04
--- /dev/null
@@ -0,0 +1,390 @@
+/* $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);
+}
diff --git a/lib/mkstemp.c b/lib/mkstemp.c
new file mode 100644 (file)
index 0000000..fe4f385
--- /dev/null
@@ -0,0 +1,79 @@
+/*  $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;
+}
diff --git a/lib/mmap.c b/lib/mmap.c
new file mode 100644 (file)
index 0000000..3eaacc6
--- /dev/null
@@ -0,0 +1,36 @@
+/*  $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);
+    }
+}
diff --git a/lib/parsedate.y b/lib/parsedate.y
new file mode 100644 (file)
index 0000000..1cad6f8
--- /dev/null
@@ -0,0 +1,861 @@
+%{
+/*  $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) */
diff --git a/lib/perl.c b/lib/perl.c
new file mode 100644 (file)
index 0000000..fe26ce4
--- /dev/null
@@ -0,0 +1,357 @@
+/*  $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) */
diff --git a/lib/pread.c b/lib/pread.c
new file mode 100644 (file)
index 0000000..4aafa9d
--- /dev/null
@@ -0,0 +1,48 @@
+/*  $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;
+}
diff --git a/lib/pwrite.c b/lib/pwrite.c
new file mode 100644 (file)
index 0000000..ee4ccc5
--- /dev/null
@@ -0,0 +1,48 @@
+/*  $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;
+}
diff --git a/lib/qio.c b/lib/qio.c
new file mode 100644 (file)
index 0000000..8a52f87
--- /dev/null
+++ b/lib/qio.c
@@ -0,0 +1,192 @@
+/*  $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;
+    }
+}
diff --git a/lib/radix32.c b/lib/radix32.c
new file mode 100644 (file)
index 0000000..7a7e503
--- /dev/null
@@ -0,0 +1,64 @@
+/*  $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 */
diff --git a/lib/readin.c b/lib/readin.c
new file mode 100644 (file)
index 0000000..fc63313
--- /dev/null
@@ -0,0 +1,84 @@
+/*  $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;
+}
diff --git a/lib/remopen.c b/lib/remopen.c
new file mode 100644 (file)
index 0000000..ab8a770
--- /dev/null
@@ -0,0 +1,216 @@
+/*  $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);
+}
diff --git a/lib/reservedfd.c b/lib/reservedfd.c
new file mode 100644 (file)
index 0000000..c4639d5
--- /dev/null
@@ -0,0 +1,91 @@
+/*  $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;
+}
diff --git a/lib/resource.c b/lib/resource.c
new file mode 100644 (file)
index 0000000..0603daa
--- /dev/null
@@ -0,0 +1,52 @@
+/*  $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 */
diff --git a/lib/sendarticle.c b/lib/sendarticle.c
new file mode 100644 (file)
index 0000000..4e514fb
--- /dev/null
@@ -0,0 +1,39 @@
+/*  $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;
+}
diff --git a/lib/sendpass.c b/lib/sendpass.c
new file mode 100644 (file)
index 0000000..a229f8c
--- /dev/null
@@ -0,0 +1,108 @@
+/*  $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;
+}
diff --git a/lib/sequence.c b/lib/sequence.c
new file mode 100644 (file)
index 0000000..7e25876
--- /dev/null
@@ -0,0 +1,37 @@
+/*  $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;
+}
diff --git a/lib/setenv.c b/lib/setenv.c
new file mode 100644 (file)
index 0000000..51ca761
--- /dev/null
@@ -0,0 +1,52 @@
+/*  $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). */
+}
diff --git a/lib/seteuid.c b/lib/seteuid.c
new file mode 100644 (file)
index 0000000..ade8b73
--- /dev/null
@@ -0,0 +1,34 @@
+/*  $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);
+}
diff --git a/lib/setproctitle.c b/lib/setproctitle.c
new file mode 100644 (file)
index 0000000..cb72960
--- /dev/null
@@ -0,0 +1,106 @@
+/*  $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 */
diff --git a/lib/snprintf.c b/lib/snprintf.c
new file mode 100644 (file)
index 0000000..9d2f759
--- /dev/null
@@ -0,0 +1,871 @@
+/*  $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 */
diff --git a/lib/sockaddr.c b/lib/sockaddr.c
new file mode 100644 (file)
index 0000000..4e5872a
--- /dev/null
@@ -0,0 +1,47 @@
+/*  $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;
+}
diff --git a/lib/strcasecmp.c b/lib/strcasecmp.c
new file mode 100644 (file)
index 0000000..75a43df
--- /dev/null
@@ -0,0 +1,103 @@
+#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);
+}
diff --git a/lib/strerror.c b/lib/strerror.c
new file mode 100644 (file)
index 0000000..7b4a472
--- /dev/null
@@ -0,0 +1,57 @@
+/*  $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;
+}
diff --git a/lib/strlcat.c b/lib/strlcat.c
new file mode 100644 (file)
index 0000000..2d00583
--- /dev/null
@@ -0,0 +1,40 @@
+/*  $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;
+}
diff --git a/lib/strlcpy.c b/lib/strlcpy.c
new file mode 100644 (file)
index 0000000..6f6c214
--- /dev/null
@@ -0,0 +1,38 @@
+/*  $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;
+}
diff --git a/lib/strspn.c b/lib/strspn.c
new file mode 100644 (file)
index 0000000..7337000
--- /dev/null
@@ -0,0 +1,59 @@
+/*  $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);
+}
diff --git a/lib/strtok.c b/lib/strtok.c
new file mode 100644 (file)
index 0000000..98470fe
--- /dev/null
@@ -0,0 +1,87 @@
+/*  $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 */
+}
diff --git a/lib/timer.c b/lib/timer.c
new file mode 100644 (file)
index 0000000..c1bdf61
--- /dev/null
@@ -0,0 +1,375 @@
+/*  $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);
+}
diff --git a/lib/tst.c b/lib/tst.c
new file mode 100644 (file)
index 0000000..15a4356
--- /dev/null
+++ b/lib/tst.c
@@ -0,0 +1,408 @@
+/*  $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 = &current_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 = &current_node->left;
+            else
+                current_node = current_node->left;
+        } else {
+            if (current_node->right == NULL)
+                root_node = &current_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);
+}
diff --git a/lib/uwildmat.c b/lib/uwildmat.c
new file mode 100644 (file)
index 0000000..55c159b
--- /dev/null
@@ -0,0 +1,401 @@
+/*  $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);
+    }
+}
diff --git a/lib/vector.c b/lib/vector.c
new file mode 100644 (file)
index 0000000..7efecf2
--- /dev/null
@@ -0,0 +1,409 @@
+/*  $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;
+}
diff --git a/lib/version.c b/lib/version.c
new file mode 100644 (file)
index 0000000..931280d
--- /dev/null
@@ -0,0 +1,13 @@
+/*  $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;
diff --git a/lib/wire.c b/lib/wire.c
new file mode 100644 (file)
index 0000000..cae1071
--- /dev/null
@@ -0,0 +1,174 @@
+/*  $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;
+}
diff --git a/lib/xfopena.c b/lib/xfopena.c
new file mode 100644 (file)
index 0000000..220927f
--- /dev/null
@@ -0,0 +1,22 @@
+/*  $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;
+}
diff --git a/lib/xmalloc.c b/lib/xmalloc.c
new file mode 100644 (file)
index 0000000..88a9545
--- /dev/null
@@ -0,0 +1,139 @@
+/* $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;
+}
diff --git a/lib/xsignal.c b/lib/xsignal.c
new file mode 100644 (file)
index 0000000..f2c1461
--- /dev/null
@@ -0,0 +1,75 @@
+/*  $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 */
diff --git a/lib/xwrite.c b/lib/xwrite.c
new file mode 100644 (file)
index 0000000..67c3d97
--- /dev/null
@@ -0,0 +1,177 @@
+/*  $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;
+}
diff --git a/nnrpd/Makefile b/nnrpd/Makefile
new file mode 100644 (file)
index 0000000..1360933
--- /dev/null
@@ -0,0 +1,194 @@
+##  $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
diff --git a/nnrpd/article.c b/nnrpd/article.c
new file mode 100644 (file)
index 0000000..e0f5b05
--- /dev/null
@@ -0,0 +1,1105 @@
+/*  $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);
+}
diff --git a/nnrpd/cache.c b/nnrpd/cache.c
new file mode 100644 (file)
index 0000000..8ad2874
--- /dev/null
@@ -0,0 +1,123 @@
+/*  $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;
+}
diff --git a/nnrpd/cache.h b/nnrpd/cache.h
new file mode 100644 (file)
index 0000000..6c561cd
--- /dev/null
@@ -0,0 +1,14 @@
+#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 */
diff --git a/nnrpd/commands.c b/nnrpd/commands.c
new file mode 100644 (file)
index 0000000..72fea77
--- /dev/null
@@ -0,0 +1,639 @@
+/*  $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);
+}
diff --git a/nnrpd/group.c b/nnrpd/group.c
new file mode 100644 (file)
index 0000000..9429245
--- /dev/null
@@ -0,0 +1,262 @@
+/*  $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");
+}
diff --git a/nnrpd/line.c b/nnrpd/line.c
new file mode 100644 (file)
index 0000000..67f210f
--- /dev/null
@@ -0,0 +1,248 @@
+/*  $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;
+}
diff --git a/nnrpd/list.c b/nnrpd/list.c
new file mode 100644 (file)
index 0000000..d03fbbb
--- /dev/null
@@ -0,0 +1,284 @@
+/*  $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");
+}
diff --git a/nnrpd/misc.c b/nnrpd/misc.c
new file mode 100644 (file)
index 0000000..fc91471
--- /dev/null
@@ -0,0 +1,552 @@
+/*  $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 */
diff --git a/nnrpd/newnews.c b/nnrpd/newnews.c
new file mode 100644 (file)
index 0000000..0ab87d5
--- /dev/null
@@ -0,0 +1,301 @@
+/*  $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);
+}
diff --git a/nnrpd/nnrpd.c b/nnrpd/nnrpd.c
new file mode 100644 (file)
index 0000000..010aa66
--- /dev/null
@@ -0,0 +1,1362 @@
+/*  $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;
+}
diff --git a/nnrpd/nnrpd.h b/nnrpd/nnrpd.h
new file mode 100644 (file)
index 0000000..1679513
--- /dev/null
@@ -0,0 +1,287 @@
+/*  $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 *);
diff --git a/nnrpd/perl.c b/nnrpd/perl.c
new file mode 100644 (file)
index 0000000..2d967dc
--- /dev/null
@@ -0,0 +1,412 @@
+/*  $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 */
diff --git a/nnrpd/perm.c b/nnrpd/perm.c
new file mode 100644 (file)
index 0000000..50a04cc
--- /dev/null
@@ -0,0 +1,2362 @@
+/*  $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);
+}
diff --git a/nnrpd/post.c b/nnrpd/post.c
new file mode 100644 (file)
index 0000000..15546f3
--- /dev/null
@@ -0,0 +1,1281 @@
+/*  $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;
+}
diff --git a/nnrpd/post.h b/nnrpd/post.h
new file mode 100644 (file)
index 0000000..9799205
--- /dev/null
@@ -0,0 +1,52 @@
+/*  $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
diff --git a/nnrpd/python.c b/nnrpd/python.c
new file mode 100644 (file)
index 0000000..a68e85d
--- /dev/null
@@ -0,0 +1,777 @@
+/*  $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) */
diff --git a/nnrpd/sasl_config.c b/nnrpd/sasl_config.c
new file mode 100644 (file)
index 0000000..e356e57
--- /dev/null
@@ -0,0 +1,142 @@
+/* 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 */
diff --git a/nnrpd/sasl_config.h b/nnrpd/sasl_config.h
new file mode 100644 (file)
index 0000000..084207e
--- /dev/null
@@ -0,0 +1,25 @@
+/* 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 */
diff --git a/nnrpd/tls.c b/nnrpd/tls.c
new file mode 100644 (file)
index 0000000..f31421a
--- /dev/null
@@ -0,0 +1,713 @@
+/* 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 */
diff --git a/nnrpd/tls.h b/nnrpd/tls.h
new file mode 100644 (file)
index 0000000..8837b3f
--- /dev/null
@@ -0,0 +1,52 @@
+/* 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 */
diff --git a/nnrpd/track.c b/nnrpd/track.c
new file mode 100644 (file)
index 0000000..4090147
--- /dev/null
@@ -0,0 +1,67 @@
+/*  $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;
+}
diff --git a/samples/INN.py b/samples/INN.py
new file mode 100644 (file)
index 0000000..964ad34
--- /dev/null
@@ -0,0 +1,39 @@
+##  $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)
diff --git a/samples/Makefile b/samples/Makefile
new file mode 100644 (file)
index 0000000..bcda7c5
--- /dev/null
@@ -0,0 +1,44 @@
+##  $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
diff --git a/samples/active.minimal b/samples/active.minimal
new file mode 100644 (file)
index 0000000..1d0cbe0
--- /dev/null
@@ -0,0 +1,6 @@
+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
diff --git a/samples/actsync.cfg b/samples/actsync.cfg
new file mode 100644 (file)
index 0000000..48975c1
--- /dev/null
@@ -0,0 +1,6 @@
+# $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
diff --git a/samples/actsync.ign b/samples/actsync.ign
new file mode 100644 (file)
index 0000000..7572ce0
--- /dev/null
@@ -0,0 +1,30 @@
+# $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
diff --git a/samples/buffindexed.conf b/samples/buffindexed.conf
new file mode 100644 (file)
index 0000000..f61dac0
--- /dev/null
@@ -0,0 +1,11 @@
+#
+# 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
diff --git a/samples/control.ctl b/samples/control.ctl
new file mode 100644 (file)
index 0000000..50c5c10
--- /dev/null
@@ -0,0 +1,2535 @@
+##  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
diff --git a/samples/cycbuff.conf b/samples/cycbuff.conf
new file mode 100644 (file)
index 0000000..3dec250
--- /dev/null
@@ -0,0 +1,26 @@
+#
+# 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
diff --git a/samples/distrib.pats b/samples/distrib.pats
new file mode 100644 (file)
index 0000000..7029eef
--- /dev/null
@@ -0,0 +1,13 @@
+##  $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
diff --git a/samples/expire.ctl b/samples/expire.ctl
new file mode 100644 (file)
index 0000000..dc47af3
--- /dev/null
@@ -0,0 +1,32 @@
+##  $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
diff --git a/samples/filter.tcl b/samples/filter.tcl
new file mode 100644 (file)
index 0000000..7338884
--- /dev/null
@@ -0,0 +1,19 @@
+# -*- 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"
+}
diff --git a/samples/filter_innd.pl b/samples/filter_innd.pl
new file mode 100644 (file)
index 0000000..a56b908
--- /dev/null
@@ -0,0 +1,212 @@
+#
+# $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.
+
diff --git a/samples/filter_innd.py b/samples/filter_innd.py
new file mode 100644 (file)
index 0000000..21d094f
--- /dev/null
@@ -0,0 +1,274 @@
+##  $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])
+
diff --git a/samples/filter_nnrpd.pl b/samples/filter_nnrpd.pl
new file mode 100644 (file)
index 0000000..6591b06
--- /dev/null
@@ -0,0 +1,74 @@
+#
+# $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);
+}
diff --git a/samples/incoming.conf b/samples/incoming.conf
new file mode 100644 (file)
index 0000000..c4b75c2
--- /dev/null
@@ -0,0 +1,142 @@
+##  $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"
+}
diff --git a/samples/inn.conf.in b/samples/inn.conf.in
new file mode 100644 (file)
index 0000000..8e0f63b
--- /dev/null
@@ -0,0 +1,185 @@
+##  $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@
diff --git a/samples/innfeed.conf b/samples/innfeed.conf
new file mode 100644 (file)
index 0000000..e350dda
--- /dev/null
@@ -0,0 +1,135 @@
+# $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'
diff --git a/samples/innreport.conf.in b/samples/innreport.conf.in
new file mode 100644 (file)
index 0000000..b6899fa
--- /dev/null
@@ -0,0 +1,2479 @@
+##########################################################
+# 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)";
+       };
+};
diff --git a/samples/innwatch.ctl b/samples/innwatch.ctl
new file mode 100644 (file)
index 0000000..39195e7
--- /dev/null
@@ -0,0 +1,40 @@
+##  $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)
diff --git a/samples/moderators b/samples/moderators
new file mode 100644 (file)
index 0000000..b85c6e4
--- /dev/null
@@ -0,0 +1,33 @@
+##  $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
diff --git a/samples/motd.news b/samples/motd.news
new file mode 100644 (file)
index 0000000..ac880ea
--- /dev/null
@@ -0,0 +1,3 @@
+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.
diff --git a/samples/news2mail.cf b/samples/news2mail.cf
new file mode 100644 (file)
index 0000000..4b89b01
--- /dev/null
@@ -0,0 +1,20 @@
+# 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
diff --git a/samples/newsfeeds.in b/samples/newsfeeds.in
new file mode 100644 (file)
index 0000000..bc05ddd
--- /dev/null
@@ -0,0 +1,129 @@
+##  $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
diff --git a/samples/newsgroups.minimal b/samples/newsgroups.minimal
new file mode 100644 (file)
index 0000000..2aeb724
--- /dev/null
@@ -0,0 +1,6 @@
+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).
diff --git a/samples/nnrpd.py b/samples/nnrpd.py
new file mode 100644 (file)
index 0000000..0094836
--- /dev/null
@@ -0,0 +1,18 @@
+##  $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)
diff --git a/samples/nnrpd.track b/samples/nnrpd.track
new file mode 100644 (file)
index 0000000..edec23f
--- /dev/null
@@ -0,0 +1,14 @@
+##  $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
diff --git a/samples/nnrpd_access.pl.in b/samples/nnrpd_access.pl.in
new file mode 100644 (file)
index 0000000..bc3d8c9
--- /dev/null
@@ -0,0 +1,121 @@
+#! /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;
+}
diff --git a/samples/nnrpd_access.py b/samples/nnrpd_access.py
new file mode 100644 (file)
index 0000000..23cd0c2
--- /dev/null
@@ -0,0 +1,92 @@
+##  $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])
diff --git a/samples/nnrpd_access_wrapper.pl.in b/samples/nnrpd_access_wrapper.pl.in
new file mode 100644 (file)
index 0000000..2a2a388
--- /dev/null
@@ -0,0 +1,48 @@
+#! /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;    
+}
diff --git a/samples/nnrpd_access_wrapper.py b/samples/nnrpd_access_wrapper.py
new file mode 100644 (file)
index 0000000..bc83012
--- /dev/null
@@ -0,0 +1,64 @@
+##  $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])
diff --git a/samples/nnrpd_auth.pl.in b/samples/nnrpd_auth.pl.in
new file mode 100644 (file)
index 0000000..bce456f
--- /dev/null
@@ -0,0 +1,68 @@
+#! /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}, "");
+}
diff --git a/samples/nnrpd_auth.py b/samples/nnrpd_auth.py
new file mode 100644 (file)
index 0000000..76ca010
--- /dev/null
@@ -0,0 +1,106 @@
+##  $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])
diff --git a/samples/nnrpd_auth_wrapper.pl.in b/samples/nnrpd_auth_wrapper.pl.in
new file mode 100644 (file)
index 0000000..50bd1e7
--- /dev/null
@@ -0,0 +1,49 @@
+#! /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;
+}
diff --git a/samples/nnrpd_auth_wrapper.py b/samples/nnrpd_auth_wrapper.py
new file mode 100644 (file)
index 0000000..d55d942
--- /dev/null
@@ -0,0 +1,62 @@
+##  $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])
diff --git a/samples/nnrpd_dynamic.py b/samples/nnrpd_dynamic.py
new file mode 100644 (file)
index 0000000..139f8b8
--- /dev/null
@@ -0,0 +1,99 @@
+##  $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])
diff --git a/samples/nnrpd_dynamic_wrapper.py b/samples/nnrpd_dynamic_wrapper.py
new file mode 100644 (file)
index 0000000..6c30e9d
--- /dev/null
@@ -0,0 +1,57 @@
+##  $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])
diff --git a/samples/nntpsend.ctl b/samples/nntpsend.ctl
new file mode 100644 (file)
index 0000000..f54a96c
--- /dev/null
@@ -0,0 +1,15 @@
+##  $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:
diff --git a/samples/ovdb.conf b/samples/ovdb.conf
new file mode 100644 (file)
index 0000000..b97432a
--- /dev/null
@@ -0,0 +1,104 @@
+# 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
+
diff --git a/samples/overview.fmt b/samples/overview.fmt
new file mode 100644 (file)
index 0000000..e3cb43d
--- /dev/null
@@ -0,0 +1,16 @@
+##  $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
diff --git a/samples/passwd.nntp b/samples/passwd.nntp
new file mode 100644 (file)
index 0000000..50cf3f5
--- /dev/null
@@ -0,0 +1,14 @@
+##  $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
diff --git a/samples/radius.conf b/samples/radius.conf
new file mode 100644 (file)
index 0000000..dd1d06c
--- /dev/null
@@ -0,0 +1,63 @@
+# $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
+
+}
diff --git a/samples/readers.conf b/samples/readers.conf
new file mode 100644 (file)
index 0000000..ed86f99
--- /dev/null
@@ -0,0 +1,136 @@
+##  $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.*"
+#}
diff --git a/samples/sasl.conf.in b/samples/sasl.conf.in
new file mode 100644 (file)
index 0000000..8f94375
--- /dev/null
@@ -0,0 +1,3 @@
+tls_ca_path:           @prefix@/lib
+tls_cert_file:         @prefix@/lib/cert.pem
+tls_key_file:          @prefix@/lib/cert.pem
diff --git a/samples/startup.tcl b/samples/startup.tcl
new file mode 100644 (file)
index 0000000..e11eeaf
--- /dev/null
@@ -0,0 +1,15 @@
+##  $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 {} {
+}
diff --git a/samples/startup_innd.pl b/samples/startup_innd.pl
new file mode 100644 (file)
index 0000000..4148469
--- /dev/null
@@ -0,0 +1,46 @@
+# 
+# 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++ ;
+       }
+}
+
diff --git a/samples/storage.conf b/samples/storage.conf
new file mode 100644 (file)
index 0000000..41a6e13
--- /dev/null
@@ -0,0 +1,45 @@
+##  $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
+#}
diff --git a/samples/subscriptions b/samples/subscriptions
new file mode 100644 (file)
index 0000000..49109a2
--- /dev/null
@@ -0,0 +1,6 @@
+news.announce.newusers
+news.newusers.questions
+misc.test
+misc.test.moderated
+news.announce.newgroups
+news.answers
diff --git a/scripts/Makefile b/scripts/Makefile
new file mode 100644 (file)
index 0000000..3da914c
--- /dev/null
@@ -0,0 +1,79 @@
+##  $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
diff --git a/scripts/inncheck.in b/scripts/inncheck.in
new file mode 100644 (file)
index 0000000..aa73fbe
--- /dev/null
@@ -0,0 +1,937 @@
+#!@_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();
+}
diff --git a/scripts/innmail.in b/scripts/innmail.in
new file mode 100644 (file)
index 0000000..7d980d8
--- /dev/null
@@ -0,0 +1,78 @@
+#! /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 ;
diff --git a/scripts/innreport.in b/scripts/innreport.in
new file mode 100644 (file)
index 0000000..334f748
--- /dev/null
@@ -0,0 +1,2598 @@
+#! /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 &lt;<A HREF=\"mailto:fta\@sofaraway.org\">";
+  $result .= "fta\@sofaraway.org</A>&gt;.\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/\&amp;/&/go;
+    $Title =~ s/\&lt;/</go;
+    $Title =~ s/\&gt;/>/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/&/\&amp;/g;
+       $unrecognize[$l] =~ s/</\&lt;/g;
+       $unrecognize[$l] =~ s/>/\&gt;/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
+&lt;<A HREF="mailto:fta\@sofaraway.org">fta\@sofaraway.org</A>&gt;.
+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 ##########################
diff --git a/scripts/innreport_inn.pm b/scripts/innreport_inn.pm
new file mode 100644 (file)
index 0000000..d2943fe
--- /dev/null
@@ -0,0 +1,2212 @@
+##########################################################
+# 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;
diff --git a/scripts/innshellvars.in b/scripts/innshellvars.in
new file mode 100644 (file)
index 0000000..4833b4d
--- /dev/null
@@ -0,0 +1,114 @@
+#!@_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
diff --git a/scripts/innshellvars.pl.in b/scripts/innshellvars.pl.in
new file mode 100644 (file)
index 0000000..422b61a
--- /dev/null
@@ -0,0 +1,116 @@
+# 
+# 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 ;
diff --git a/scripts/innshellvars.tcl.in b/scripts/innshellvars.tcl.in
new file mode 100644 (file)
index 0000000..c5e2dea
--- /dev/null
@@ -0,0 +1,105 @@
+# -*- 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"
diff --git a/scripts/innstat.in b/scripts/innstat.in
new file mode 100644 (file)
index 0000000..2c16d07
--- /dev/null
@@ -0,0 +1,79 @@
+#! /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;
+     }'
+
diff --git a/scripts/innupgrade.in b/scripts/innupgrade.in
new file mode 100644 (file)
index 0000000..ea9251f
--- /dev/null
@@ -0,0 +1,152 @@
+#! /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);
+}
diff --git a/scripts/innwatch.in b/scripts/innwatch.in
new file mode 100644 (file)
index 0000000..06cf95b
--- /dev/null
@@ -0,0 +1,334 @@
+#! /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}
diff --git a/scripts/news.daily.in b/scripts/news.daily.in
new file mode 100644 (file)
index 0000000..2abb9be
--- /dev/null
@@ -0,0 +1,537 @@
+#!@_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
diff --git a/scripts/rc.news.in b/scripts/rc.news.in
new file mode 100644 (file)
index 0000000..59eb7fe
--- /dev/null
@@ -0,0 +1,186 @@
+#! /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
diff --git a/scripts/scanlogs.in b/scripts/scanlogs.in
new file mode 100644 (file)
index 0000000..6101899
--- /dev/null
@@ -0,0 +1,331 @@
+#! /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
diff --git a/scripts/simpleftp.in b/scripts/simpleftp.in
new file mode 100644 (file)
index 0000000..ef133bc
--- /dev/null
@@ -0,0 +1,90 @@
+#! /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;
diff --git a/scripts/tally.control.in b/scripts/tally.control.in
new file mode 100644 (file)
index 0000000..3e49ce5
--- /dev/null
@@ -0,0 +1,59 @@
+#! /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
diff --git a/scripts/writelog.in b/scripts/writelog.in
new file mode 100644 (file)
index 0000000..a177ec6
--- /dev/null
@@ -0,0 +1,56 @@
+#! /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
diff --git a/site/Makefile b/site/Makefile
new file mode 100644 (file)
index 0000000..4df27f7
--- /dev/null
@@ -0,0 +1,268 @@
+##  $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) $? $@
diff --git a/site/getsafe.sh b/site/getsafe.sh
new file mode 100644 (file)
index 0000000..4f123fb
--- /dev/null
@@ -0,0 +1,46 @@
+#! /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
diff --git a/storage/Make.methods b/storage/Make.methods
new file mode 100644 (file)
index 0000000..fc84d6c
--- /dev/null
@@ -0,0 +1,31 @@
+# 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)
diff --git a/storage/Makefile b/storage/Makefile
new file mode 100644 (file)
index 0000000..770bd23
--- /dev/null
@@ -0,0 +1,225 @@
+##  $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
diff --git a/storage/buffindexed/buffindexed.c b/storage/buffindexed/buffindexed.c
new file mode 100644 (file)
index 0000000..a5498e8
--- /dev/null
@@ -0,0 +1,2275 @@
+/*  $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 */
diff --git a/storage/buffindexed/buffindexed.h b/storage/buffindexed/buffindexed.h
new file mode 100644 (file)
index 0000000..17ab69c
--- /dev/null
@@ -0,0 +1,26 @@
+#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_ */
diff --git a/storage/buffindexed/ovmethod.config b/storage/buffindexed/ovmethod.config
new file mode 100644 (file)
index 0000000..a26410c
--- /dev/null
@@ -0,0 +1,3 @@
+name    = buffindexed
+number  = 3
+sources = buffindexed.c
diff --git a/storage/buffindexed/ovmethod.mk b/storage/buffindexed/ovmethod.mk
new file mode 100644 (file)
index 0000000..578c466
--- /dev/null
@@ -0,0 +1,8 @@
+# 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)
diff --git a/storage/buildconfig.in b/storage/buildconfig.in
new file mode 100644 (file)
index 0000000..22d2188
--- /dev/null
@@ -0,0 +1,258 @@
+#! /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);
diff --git a/storage/cnfs/cnfs-private.h b/storage/cnfs/cnfs-private.h
new file mode 100644 (file)
index 0000000..c899449
--- /dev/null
@@ -0,0 +1,111 @@
+/*  $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 */
diff --git a/storage/cnfs/cnfs.c b/storage/cnfs/cnfs.c
new file mode 100644 (file)
index 0000000..66e00a6
--- /dev/null
@@ -0,0 +1,1771 @@
+/*  $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();
+}
diff --git a/storage/cnfs/cnfs.h b/storage/cnfs/cnfs.h
new file mode 100644 (file)
index 0000000..fc7f1c9
--- /dev/null
@@ -0,0 +1,20 @@
+/*  $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
diff --git a/storage/cnfs/method.config b/storage/cnfs/method.config
new file mode 100644 (file)
index 0000000..2ba88a6
--- /dev/null
@@ -0,0 +1,3 @@
+name    = cnfs
+number  = 3
+sources = cnfs.c
diff --git a/storage/expire.c b/storage/expire.c
new file mode 100644 (file)
index 0000000..37f4a80
--- /dev/null
@@ -0,0 +1,906 @@
+/*  $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;
+        }
+    }
+}
diff --git a/storage/interface.c b/storage/interface.c
new file mode 100644 (file)
index 0000000..dde700d
--- /dev/null
@@ -0,0 +1,962 @@
+/*  $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;
+    }
+}
+
diff --git a/storage/interface.h b/storage/interface.h
new file mode 100644 (file)
index 0000000..b59a783
--- /dev/null
@@ -0,0 +1,58 @@
+/*  $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__ */
diff --git a/storage/ov.c b/storage/ov.c
new file mode 100644 (file)
index 0000000..37288aa
--- /dev/null
@@ -0,0 +1,372 @@
+/*  $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();
+}
diff --git a/storage/ovdb/ovdb-private.h b/storage/ovdb/ovdb-private.h
new file mode 100644 (file)
index 0000000..54be538
--- /dev/null
@@ -0,0 +1,233 @@
+#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 */
diff --git a/storage/ovdb/ovdb.c b/storage/ovdb/ovdb.c
new file mode 100644 (file)
index 0000000..bd03481
--- /dev/null
@@ -0,0 +1,3004 @@
+/*
+ * 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 */
+
diff --git a/storage/ovdb/ovdb.h b/storage/ovdb/ovdb.h
new file mode 100644 (file)
index 0000000..6b1d1eb
--- /dev/null
@@ -0,0 +1,26 @@
+#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_ */
diff --git a/storage/ovdb/ovmethod.config b/storage/ovdb/ovmethod.config
new file mode 100644 (file)
index 0000000..076ee9b
--- /dev/null
@@ -0,0 +1,3 @@
+name    = ovdb
+number  = 4
+sources = ovdb.c
diff --git a/storage/overdata.c b/storage/overdata.c
new file mode 100644 (file)
index 0000000..b2de7c8
--- /dev/null
@@ -0,0 +1,416 @@
+/*  $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;
+}
diff --git a/storage/ovinterface.h b/storage/ovinterface.h
new file mode 100644 (file)
index 0000000..896ea7b
--- /dev/null
@@ -0,0 +1,56 @@
+/*  $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__ */
diff --git a/storage/timecaf/README.CAF b/storage/timecaf/README.CAF
new file mode 100644 (file)
index 0000000..8ac0b64
--- /dev/null
@@ -0,0 +1,125 @@
+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.]
diff --git a/storage/timecaf/caf.c b/storage/timecaf/caf.c
new file mode 100644 (file)
index 0000000..450b561
--- /dev/null
@@ -0,0 +1,1823 @@
+/*  $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;
+}
diff --git a/storage/timecaf/caf.h b/storage/timecaf/caf.h
new file mode 100644 (file)
index 0000000..babd4bd
--- /dev/null
@@ -0,0 +1,153 @@
+/* $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. */
diff --git a/storage/timecaf/method.config b/storage/timecaf/method.config
new file mode 100644 (file)
index 0000000..e42a08d
--- /dev/null
@@ -0,0 +1,3 @@
+name    = timecaf
+number  = 4
+sources = caf.c timecaf.c
diff --git a/storage/timecaf/timecaf.c b/storage/timecaf/timecaf.c
new file mode 100644 (file)
index 0000000..e053a02
--- /dev/null
@@ -0,0 +1,853 @@
+/*  $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, &timestamp, &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();
+}
diff --git a/storage/timecaf/timecaf.h b/storage/timecaf/timecaf.h
new file mode 100644 (file)
index 0000000..fd2dd34
--- /dev/null
@@ -0,0 +1,25 @@
+/*  $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
diff --git a/storage/timehash/method.config b/storage/timehash/method.config
new file mode 100644 (file)
index 0000000..c4654bd
--- /dev/null
@@ -0,0 +1,3 @@
+name    = timehash
+number  = 2
+sources = timehash.c
diff --git a/storage/timehash/timehash.c b/storage/timehash/timehash.c
new file mode 100644 (file)
index 0000000..82c5942
--- /dev/null
@@ -0,0 +1,530 @@
+/*  $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) {
+}
diff --git a/storage/timehash/timehash.h b/storage/timehash/timehash.h
new file mode 100644 (file)
index 0000000..dc2e246
--- /dev/null
@@ -0,0 +1,23 @@
+/*  $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
diff --git a/storage/tradindexed/ovmethod.config b/storage/tradindexed/ovmethod.config
new file mode 100644 (file)
index 0000000..ec9f310
--- /dev/null
@@ -0,0 +1,5 @@
+name          = tradindexed
+number        = 2
+sources       = tdx-cache.c tdx-group.c tdx-data.c tradindexed.c
+extra-sources = tdx-util.c
+programs      = tdx-util
diff --git a/storage/tradindexed/ovmethod.mk b/storage/tradindexed/ovmethod.mk
new file mode 100644 (file)
index 0000000..8038de4
--- /dev/null
@@ -0,0 +1,6 @@
+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)
diff --git a/storage/tradindexed/tdx-cache.c b/storage/tradindexed/tdx-cache.c
new file mode 100644 (file)
index 0000000..93abee6
--- /dev/null
@@ -0,0 +1,214 @@
+/*  $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);
+}
diff --git a/storage/tradindexed/tdx-data.c b/storage/tradindexed/tdx-data.c
new file mode 100644 (file)
index 0000000..e64b927
--- /dev/null
@@ -0,0 +1,1090 @@
+/*  $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);
+}
diff --git a/storage/tradindexed/tdx-group.c b/storage/tradindexed/tdx-group.c
new file mode 100644 (file)
index 0000000..82af513
--- /dev/null
@@ -0,0 +1,1424 @@
+/*  $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);
+}
diff --git a/storage/tradindexed/tdx-private.h b/storage/tradindexed/tdx-private.h
new file mode 100644 (file)
index 0000000..42e4701
--- /dev/null
@@ -0,0 +1,176 @@
+/*  $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 */
diff --git a/storage/tradindexed/tdx-structure.h b/storage/tradindexed/tdx-structure.h
new file mode 100644 (file)
index 0000000..af9868c
--- /dev/null
@@ -0,0 +1,128 @@
+/*  $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 */
diff --git a/storage/tradindexed/tdx-util.c b/storage/tradindexed/tdx-util.c
new file mode 100644 (file)
index 0000000..c1b004b
--- /dev/null
@@ -0,0 +1,469 @@
+/*  $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);
+}
diff --git a/storage/tradindexed/tradindexed.c b/storage/tradindexed/tradindexed.c
new file mode 100644 (file)
index 0000000..b21cd0b
--- /dev/null
@@ -0,0 +1,408 @@
+/*  $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;
+    }
+}
diff --git a/storage/tradindexed/tradindexed.h b/storage/tradindexed/tradindexed.h
new file mode 100644 (file)
index 0000000..ffb2908
--- /dev/null
@@ -0,0 +1,40 @@
+/*  $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 */
diff --git a/storage/tradspool/README.tradspool b/storage/tradspool/README.tradspool
new file mode 100644 (file)
index 0000000..d40e19b
--- /dev/null
@@ -0,0 +1,43 @@
+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. 
+
diff --git a/storage/tradspool/method.config b/storage/tradspool/method.config
new file mode 100644 (file)
index 0000000..100865d
--- /dev/null
@@ -0,0 +1,3 @@
+name    = tradspool
+number  = 5
+sources = tradspool.c
diff --git a/storage/tradspool/tradspool.c b/storage/tradspool/tradspool.c
new file mode 100644 (file)
index 0000000..0df3598
--- /dev/null
@@ -0,0 +1,1313 @@
+/*  $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();
+}
diff --git a/storage/tradspool/tradspool.h b/storage/tradspool/tradspool.h
new file mode 100644 (file)
index 0000000..582e740
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+** $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
diff --git a/storage/trash/method.config b/storage/trash/method.config
new file mode 100644 (file)
index 0000000..f7b73cb
--- /dev/null
@@ -0,0 +1,3 @@
+name    = trash
+number  = 0
+sources = trash.c
diff --git a/storage/trash/trash.c b/storage/trash/trash.c
new file mode 100644 (file)
index 0000000..7dfe1bf
--- /dev/null
@@ -0,0 +1,94 @@
+/*  $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)
+{
+}
diff --git a/storage/trash/trash.h b/storage/trash/trash.h
new file mode 100644 (file)
index 0000000..e19a77b
--- /dev/null
@@ -0,0 +1,23 @@
+/*  $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
diff --git a/support/config.guess b/support/config.guess
new file mode 100755 (executable)
index 0000000..c7607c7
--- /dev/null
@@ -0,0 +1,1526 @@
+#! /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:
diff --git a/support/config.sub b/support/config.sub
new file mode 100755 (executable)
index 0000000..63bfff0
--- /dev/null
@@ -0,0 +1,1669 @@
+#! /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:
diff --git a/support/fixscript.in b/support/fixscript.in
new file mode 100644 (file)
index 0000000..edd52fe
--- /dev/null
@@ -0,0 +1,77 @@
+#! @_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"
diff --git a/support/indent b/support/indent
new file mode 100755 (executable)
index 0000000..6b9213e
--- /dev/null
@@ -0,0 +1,29 @@
+#! /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 \
+    $*
diff --git a/support/install-sh b/support/install-sh
new file mode 100755 (executable)
index 0000000..7931d0d
--- /dev/null
@@ -0,0 +1,276 @@
+#!/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
diff --git a/support/ltmain.sh b/support/ltmain.sh
new file mode 100644 (file)
index 0000000..9d81a07
--- /dev/null
@@ -0,0 +1,4995 @@
+# 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:
diff --git a/support/makedepend b/support/makedepend
new file mode 100755 (executable)
index 0000000..8157885
--- /dev/null
@@ -0,0 +1,36 @@
+#! /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
diff --git a/support/mkchangelog b/support/mkchangelog
new file mode 100755 (executable)
index 0000000..7010078
--- /dev/null
@@ -0,0 +1,23 @@
+#! /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";
diff --git a/support/mkmanifest b/support/mkmanifest
new file mode 100755 (executable)
index 0000000..3d87a44
--- /dev/null
@@ -0,0 +1,74 @@
+#! /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), '');
diff --git a/support/mksnapshot b/support/mksnapshot
new file mode 100755 (executable)
index 0000000..a1e1631
--- /dev/null
@@ -0,0 +1,50 @@
+#! /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"
diff --git a/support/mksystem b/support/mksystem
new file mode 100755 (executable)
index 0000000..6195451
--- /dev/null
@@ -0,0 +1,47 @@
+#! /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
diff --git a/support/mkversion b/support/mkversion
new file mode 100755 (executable)
index 0000000..a32db90
--- /dev/null
@@ -0,0 +1,39 @@
+#! /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
diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644 (file)
index 0000000..97022e0
--- /dev/null
@@ -0,0 +1,179 @@
+##  $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)
diff --git a/tests/TESTS b/tests/TESTS
new file mode 100644 (file)
index 0000000..5e09a11
--- /dev/null
@@ -0,0 +1,32 @@
+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
diff --git a/tests/authprogs/ckpasswd.t b/tests/authprogs/ckpasswd.t
new file mode 100755 (executable)
index 0000000..95c9680
--- /dev/null
@@ -0,0 +1,80 @@
+#! /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"
diff --git a/tests/authprogs/domain.t b/tests/authprogs/domain.t
new file mode 100755 (executable)
index 0000000..205c70f
--- /dev/null
@@ -0,0 +1,70 @@
+#! /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"
diff --git a/tests/authprogs/passwd b/tests/authprogs/passwd
new file mode 100644 (file)
index 0000000..fefd476
--- /dev/null
@@ -0,0 +1,10 @@
+# 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::::::::::::::::::::::
diff --git a/tests/lib/articles/no-body b/tests/lib/articles/no-body
new file mode 100644 (file)
index 0000000..5187f88
--- /dev/null
@@ -0,0 +1,5 @@
+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
diff --git a/tests/lib/articles/strange b/tests/lib/articles/strange
new file mode 100644 (file)
index 0000000..3c0731a
Binary files /dev/null and b/tests/lib/articles/strange differ
diff --git a/tests/lib/articles/truncated b/tests/lib/articles/truncated
new file mode 100644 (file)
index 0000000..24f301a
--- /dev/null
@@ -0,0 +1,5 @@
+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
diff --git a/tests/lib/buffer-t.c b/tests/lib/buffer-t.c
new file mode 100644 (file)
index 0000000..50ca96d
--- /dev/null
@@ -0,0 +1,69 @@
+/* $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;
+}
diff --git a/tests/lib/concat-t.c b/tests/lib/concat-t.c
new file mode 100644 (file)
index 0000000..9d18adb
--- /dev/null
@@ -0,0 +1,31 @@
+/* $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;
+}
diff --git a/tests/lib/config/errors b/tests/lib/config/errors
new file mode 100644 (file)
index 0000000..fde6d17
--- /dev/null
@@ -0,0 +1,52 @@
+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
+===
diff --git a/tests/lib/config/line-endings b/tests/lib/config/line-endings
new file mode 100644 (file)
index 0000000..399cff0
--- /dev/null
@@ -0,0 +1,10 @@
+# This is a leading comment.
+param1: on
+param2:  true
+param3: yes
+# this is a comment \
+param4: off
+  # this is another
+int1: 0
+
+int2: -3
diff --git a/tests/lib/config/no-newline b/tests/lib/config/no-newline
new file mode 100644 (file)
index 0000000..b72eaf4
--- /dev/null
@@ -0,0 +1,2 @@
+# Test a lack of newline at the end of the configuration file.
+parameter: value
\ No newline at end of file
diff --git a/tests/lib/config/null b/tests/lib/config/null
new file mode 100644 (file)
index 0000000..ebd35f4
Binary files /dev/null and b/tests/lib/config/null differ
diff --git a/tests/lib/config/simple b/tests/lib/config/simple
new file mode 100644 (file)
index 0000000..6f21e65
--- /dev/null
@@ -0,0 +1,2 @@
+foo: baz
+bar: baz
diff --git a/tests/lib/config/valid b/tests/lib/config/valid
new file mode 100644 (file)
index 0000000..44d78dd
--- /dev/null
@@ -0,0 +1,29 @@
+# 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?"
diff --git a/tests/lib/config/warn-bool b/tests/lib/config/warn-bool
new file mode 100644 (file)
index 0000000..add8c08
--- /dev/null
@@ -0,0 +1,13 @@
+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
+===
diff --git a/tests/lib/config/warn-int b/tests/lib/config/warn-int
new file mode 100644 (file)
index 0000000..bf7beae
--- /dev/null
@@ -0,0 +1,13 @@
+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
+===
diff --git a/tests/lib/config/warnings b/tests/lib/config/warnings
new file mode 100644 (file)
index 0000000..facfd6b
--- /dev/null
@@ -0,0 +1,18 @@
+# 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
+===
diff --git a/tests/lib/confparse-t.c b/tests/lib/confparse-t.c
new file mode 100644 (file)
index 0000000..a4b4762
--- /dev/null
@@ -0,0 +1,399 @@
+/* $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;
+}
diff --git a/tests/lib/date-t.c b/tests/lib/date-t.c
new file mode 100644 (file)
index 0000000..d60de15
--- /dev/null
@@ -0,0 +1,178 @@
+/* $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(&timestamp);
+    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(&timestamp);
+    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;
+}
diff --git a/tests/lib/fakewrite.c b/tests/lib/fakewrite.c
new file mode 100644 (file)
index 0000000..7661e28
--- /dev/null
@@ -0,0 +1,101 @@
+/* $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;
+}
diff --git a/tests/lib/hash-t.c b/tests/lib/hash-t.c
new file mode 100644 (file)
index 0000000..c8bd9bc
--- /dev/null
@@ -0,0 +1,47 @@
+/* $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;
+}
diff --git a/tests/lib/hashtab-t.c b/tests/lib/hashtab-t.c
new file mode 100644 (file)
index 0000000..9f818ff
--- /dev/null
@@ -0,0 +1,191 @@
+/* $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;
+}
diff --git a/tests/lib/hstrerror-t.c b/tests/lib/hstrerror-t.c
new file mode 100644 (file)
index 0000000..64163a4
--- /dev/null
@@ -0,0 +1,32 @@
+/* $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;
+}
diff --git a/tests/lib/inet_aton-t.c b/tests/lib/inet_aton-t.c
new file mode 100644 (file)
index 0000000..59b0e75
--- /dev/null
@@ -0,0 +1,95 @@
+/* $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;
+}
diff --git a/tests/lib/inet_ntoa-t.c b/tests/lib/inet_ntoa-t.c
new file mode 100644 (file)
index 0000000..46aee06
--- /dev/null
@@ -0,0 +1,33 @@
+/* $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;
+}
diff --git a/tests/lib/innconf-t.c b/tests/lib/innconf-t.c
new file mode 100644 (file)
index 0000000..0442901
--- /dev/null
@@ -0,0 +1,64 @@
+/* $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;
+}
diff --git a/tests/lib/list-t.c b/tests/lib/list-t.c
new file mode 100644 (file)
index 0000000..5ad4d14
--- /dev/null
@@ -0,0 +1,67 @@
+/* $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;
+}
diff --git a/tests/lib/md5-t.c b/tests/lib/md5-t.c
new file mode 100644 (file)
index 0000000..0a411be
--- /dev/null
@@ -0,0 +1,141 @@
+/* $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;
+}
diff --git a/tests/lib/memcmp-t.c b/tests/lib/memcmp-t.c
new file mode 100644 (file)
index 0000000..2b59789
--- /dev/null
@@ -0,0 +1,34 @@
+/* $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;
+}
diff --git a/tests/lib/messages-t.c b/tests/lib/messages-t.c
new file mode 100644 (file)
index 0000000..2da71d2
--- /dev/null
@@ -0,0 +1,265 @@
+/* $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;
+}
diff --git a/tests/lib/mkstemp-t.c b/tests/lib/mkstemp-t.c
new file mode 100644 (file)
index 0000000..203d326
--- /dev/null
@@ -0,0 +1,65 @@
+/* $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;
+}
diff --git a/tests/lib/pread-t.c b/tests/lib/pread-t.c
new file mode 100644 (file)
index 0000000..b977215
--- /dev/null
@@ -0,0 +1,57 @@
+/* $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;
+}
diff --git a/tests/lib/pwrite-t.c b/tests/lib/pwrite-t.c
new file mode 100644 (file)
index 0000000..d329042
--- /dev/null
@@ -0,0 +1,51 @@
+/* $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;
+}
diff --git a/tests/lib/qio-t.c b/tests/lib/qio-t.c
new file mode 100644 (file)
index 0000000..b357fd5
--- /dev/null
@@ -0,0 +1,155 @@
+/* $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;
+}
diff --git a/tests/lib/setenv-t.c b/tests/lib/setenv-t.c
new file mode 100644 (file)
index 0000000..a710fb7
--- /dev/null
@@ -0,0 +1,59 @@
+/* $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;
+}
diff --git a/tests/lib/setenv.t b/tests/lib/setenv.t
new file mode 100755 (executable)
index 0000000..64d445a
--- /dev/null
@@ -0,0 +1,17 @@
+#! /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
diff --git a/tests/lib/snprintf-t.c b/tests/lib/snprintf-t.c
new file mode 100644 (file)
index 0000000..6979e48
--- /dev/null
@@ -0,0 +1,165 @@
+/* $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;
+}
diff --git a/tests/lib/strerror-t.c b/tests/lib/strerror-t.c
new file mode 100644 (file)
index 0000000..014d055
--- /dev/null
@@ -0,0 +1,29 @@
+/* $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;
+}
diff --git a/tests/lib/strlcat-t.c b/tests/lib/strlcat-t.c
new file mode 100644 (file)
index 0000000..454eb1a
--- /dev/null
@@ -0,0 +1,54 @@
+/* $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;
+}
diff --git a/tests/lib/strlcpy-t.c b/tests/lib/strlcpy-t.c
new file mode 100644 (file)
index 0000000..1d03a77
--- /dev/null
@@ -0,0 +1,49 @@
+/* $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;
+}
diff --git a/tests/lib/tst-t.c b/tests/lib/tst-t.c
new file mode 100644 (file)
index 0000000..76dcada
--- /dev/null
@@ -0,0 +1,132 @@
+/* $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;
+}
diff --git a/tests/lib/uwildmat-t.c b/tests/lib/uwildmat-t.c
new file mode 100644 (file)
index 0000000..fdad296
--- /dev/null
@@ -0,0 +1,240 @@
+/*  $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;
+}
diff --git a/tests/lib/vector-t.c b/tests/lib/vector-t.c
new file mode 100644 (file)
index 0000000..8e424c4
--- /dev/null
@@ -0,0 +1,189 @@
+/* $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;
+}
diff --git a/tests/lib/wire-t.c b/tests/lib/wire-t.c
new file mode 100644 (file)
index 0000000..66116ee
--- /dev/null
@@ -0,0 +1,123 @@
+/* $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;
+}
diff --git a/tests/lib/xmalloc.c b/tests/lib/xmalloc.c
new file mode 100644 (file)
index 0000000..4c951e2
--- /dev/null
@@ -0,0 +1,215 @@
+/* $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);
+}
diff --git a/tests/lib/xmalloc.t b/tests/lib/xmalloc.t
new file mode 100755 (executable)
index 0000000..4fb2ebd
--- /dev/null
@@ -0,0 +1,97 @@
+#! /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"
diff --git a/tests/lib/xwrite-t.c b/tests/lib/xwrite-t.c
new file mode 100644 (file)
index 0000000..e8506f6
--- /dev/null
@@ -0,0 +1,126 @@
+/* $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;
+}
diff --git a/tests/libtest.c b/tests/libtest.c
new file mode 100644 (file)
index 0000000..2a2fe7e
--- /dev/null
@@ -0,0 +1,114 @@
+/*  $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);
+}
diff --git a/tests/libtest.h b/tests/libtest.h
new file mode 100644 (file)
index 0000000..5e6e445
--- /dev/null
@@ -0,0 +1,29 @@
+/*  $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 */
diff --git a/tests/overview/data/basic b/tests/overview/data/basic
new file mode 100644 (file)
index 0000000..c59b4d3
--- /dev/null
@@ -0,0 +1,99 @@
+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
diff --git a/tests/overview/data/bogus b/tests/overview/data/bogus
new file mode 100644 (file)
index 0000000..934bb36
--- /dev/null
@@ -0,0 +1,9 @@
+..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
diff --git a/tests/overview/data/high-numbered b/tests/overview/data/high-numbered
new file mode 100644 (file)
index 0000000..4365699
--- /dev/null
@@ -0,0 +1,2 @@
+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
diff --git a/tests/overview/data/reversed b/tests/overview/data/reversed
new file mode 100644 (file)
index 0000000..aa7bd9c
--- /dev/null
@@ -0,0 +1,99 @@
+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
diff --git a/tests/overview/munge-data b/tests/overview/munge-data
new file mode 100755 (executable)
index 0000000..56d11ff
--- /dev/null
@@ -0,0 +1,43 @@
+#!/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";
+        }
+    }
+}
diff --git a/tests/overview/tradindexed-t.c b/tests/overview/tradindexed-t.c
new file mode 100644 (file)
index 0000000..c93882d
--- /dev/null
@@ -0,0 +1,507 @@
+/* $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;
+}
diff --git a/tests/runtests.c b/tests/runtests.c
new file mode 100644 (file)
index 0000000..5dca51f
--- /dev/null
@@ -0,0 +1,690 @@
+/* $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);
+}
diff --git a/tests/util/convdate.t b/tests/util/convdate.t
new file mode 100755 (executable)
index 0000000..9389f1b
--- /dev/null
@@ -0,0 +1,54 @@
+#! /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)'