summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Palfrader <peter@palfrader.org>2006-03-06 15:10:03 +0000
committerPeter Palfrader <peter@palfrader.org>2006-03-06 15:10:03 +0000
commit0a27014ee8ba24a3ca3d78cefdeda8ba391e42ba (patch)
tree2d08c30a4ece1139d9f203be2ecc3b25a45bfec4
parent0468b8a264c429c66a92ff56e012f6f794603f09 (diff)
Tag as release_2_1_8-4, againecholot-2.1.8-4
-rw-r--r--trunk/COPYING340
-rw-r--r--trunk/Echolot/Chain.pm266
-rw-r--r--trunk/Echolot/Commands.pm131
-rw-r--r--trunk/Echolot/Conf.pm531
-rw-r--r--trunk/Echolot/Config.pm344
-rw-r--r--trunk/Echolot/Fromlines.pm126
-rw-r--r--trunk/Echolot/Globals.pm60
-rw-r--r--trunk/Echolot/Log.pm163
-rw-r--r--trunk/Echolot/Mailin.pm252
-rw-r--r--trunk/Echolot/Pinger.pm211
-rw-r--r--trunk/Echolot/Pinger/CPunk.pm205
-rw-r--r--trunk/Echolot/Pinger/Mix.pm139
-rw-r--r--trunk/Echolot/Report.pm70
-rw-r--r--trunk/Echolot/Scheduler.pm196
-rw-r--r--trunk/Echolot/Stats.pm983
-rw-r--r--trunk/Echolot/Storage/File.pm1880
-rw-r--r--trunk/Echolot/Thesaurus.pm144
-rw-r--r--trunk/Echolot/Tools.pm476
-rw-r--r--trunk/LICENSE17
-rw-r--r--trunk/NEWS415
-rw-r--r--trunk/README319
-rw-r--r--trunk/TODO35
-rw-r--r--trunk/UPGRADE21
-rw-r--r--trunk/debian/README.Debian26
-rw-r--r--trunk/debian/changelog299
-rw-r--r--trunk/debian/control21
-rw-r--r--trunk/debian/copyright24
-rw-r--r--trunk/debian/echolot.default12
-rw-r--r--trunk/debian/echolot.dirs7
-rw-r--r--trunk/debian/echolot.docs3
-rwxr-xr-xtrunk/debian/echolot.init174
-rw-r--r--trunk/debian/echolot.links1
-rw-r--r--trunk/debian/echolot.logrotate11
-rw-r--r--trunk/debian/echolot.manpages2
-rwxr-xr-xtrunk/debian/echolot.postinst41
-rwxr-xr-xtrunk/debian/echolot.postrm28
-rw-r--r--trunk/debian/pingd.conf25
-rwxr-xr-xtrunk/debian/rules77
-rw-r--r--trunk/doc/methodology60
-rw-r--r--trunk/doc/pingd.conf.pod910
-rwxr-xr-xtrunk/pingd854
-rw-r--r--trunk/pingd.conf.sample72
-rw-r--r--trunk/templates/LICENSE39
-rw-r--r--trunk/templates/clist.html72
-rw-r--r--trunk/templates/echolot.css10
-rw-r--r--trunk/templates/echolot.html144
-rw-r--r--trunk/templates/fromlinesindex.html70
-rw-r--r--trunk/templates/mlist.html72
-rw-r--r--trunk/templates/mlist2.html72
-rw-r--r--trunk/templates/rlist-clear.html72
-rw-r--r--trunk/templates/rlist-dsa.html72
-rw-r--r--trunk/templates/rlist-rsa.html72
-rw-r--r--trunk/templates/rlist.html72
-rw-r--r--trunk/templates/rlist2-clear.html72
-rw-r--r--trunk/templates/rlist2-dsa.html72
-rw-r--r--trunk/templates/rlist2-rsa.html72
-rw-r--r--trunk/templates/rlist2.html72
-rw-r--r--trunk/templates/thesaurusindex.html40
-rw-r--r--trunk/templates/v1legend.html22
-rw-r--r--trunk/templates/v2legend.html85
-rwxr-xr-xtrunk/tools/create-distribution42
-rwxr-xr-xtrunk/tools/install-perl-modules33
-rwxr-xr-xtrunk/tools/pingctl171
63 files changed, 11419 insertions, 0 deletions
diff --git a/trunk/COPYING b/trunk/COPYING
new file mode 100644
index 0000000..b578119
--- /dev/null
+++ b/trunk/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 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.
+
+ 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.)
+
+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.
+
+ 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.
+
+ 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
+
+ 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) <year> <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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 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) year 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/trunk/Echolot/Chain.pm b/trunk/Echolot/Chain.pm
new file mode 100644
index 0000000..463adeb
--- /dev/null
+++ b/trunk/Echolot/Chain.pm
@@ -0,0 +1,266 @@
+package Echolot::Chain;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Chain - actual sending and receiving of Chain-Pings.
+
+=head1 DESCRIPTION
+
+This package provides functions for sending out and receiving chain-pings.
+
+=cut
+
+use strict;
+use English;
+use Echolot::Log;
+use Echolot::Pinger::Mix;
+use Echolot::Pinger::CPunk;
+
+my %INTENSIVE_CARE;
+
+sub do_mix_chainping($$$$$$$$) {
+ my ($addr1, $type1, $keyid1, $addr2, $type2, $keyid2, $to, $body) = @_;
+
+ ($type1 eq 'mix' && $type2 eq 'mix') or
+ Echolot::Log::warn("both types should really be mix ($type1, $type2)."),
+ return 0;
+
+ my %key1 = Echolot::Globals::get()->{'storage'}->get_key($addr1, $type1, $keyid1);
+ my %key2 = Echolot::Globals::get()->{'storage'}->get_key($addr2, $type2, $keyid2);
+ Echolot::Pinger::Mix::ping(
+ $body,
+ $to,
+ 0,
+ [ $key1{'nick'} , $key2{'nick'} ],
+ { $keyid1 => \%key1, $keyid2 => \%key2 } ) or
+ return 0;
+
+ return 1;
+};
+
+sub do_cpunk_chainping($$$$$$$$) {
+ my ($addr1, $type1, $keyid1, $addr2, $type2, $keyid2, $to, $body) = @_;
+
+ my $keyhash = {};
+ if ($type1 ne 'cpunk-clear') {
+ my %key = Echolot::Globals::get()->{'storage'}->get_key($addr1, $type1, $keyid1);
+ $keyhash->{$keyid1} = \%key;
+ };
+ if ($type2 ne 'cpunk-clear') {
+ my %key = Echolot::Globals::get()->{'storage'}->get_key($addr2, $type2, $keyid2);
+ $keyhash->{$keyid2} = \%key;
+ };
+ Echolot::Pinger::CPunk::ping(
+ $body,
+ $to,
+ 0,
+ [ { address => $addr1,
+ keyid => $keyid1,
+ encrypt => ($type1 ne 'cpunk-clear'),
+ pgp2compat => ($type1 eq 'cpunk-rsa') },
+ { address => $addr2,
+ keyid => $keyid2,
+ encrypt => ($type2 ne 'cpunk-clear'),
+ pgp2compat => ($type2 eq 'cpunk-rsa') } ],
+ $keyhash ) or
+ return 0;
+
+ return 1;
+};
+
+sub do_chainping($$$$$$$) {
+ my ($chaintype, $addr1, $type1, $key1, $addr2, $type2, $key2) = @_;
+
+ my $now = time();
+ my $token = join(':', $chaintype, $addr1, $type1, $key1, $addr2, $type2, $key2, $now);
+ my $mac = Echolot::Tools::make_mac($token);
+ my $body = "chaintype: $chaintype\n".
+ "remailer1: $addr1\n".
+ "type1: $type1\n".
+ "key1: $key1\n".
+ "remailer2: $addr2\n".
+ "type2: $type2\n".
+ "key2: $key2\n".
+ "sent: $now\n".
+ "mac: $mac\n".
+ Echolot::Tools::make_garbage();
+ $body = Echolot::Tools::crypt_symmetrically($body, 'encrypt');
+
+ my $to = Echolot::Tools::make_address('chainping');
+ if ($chaintype eq 'mix') {
+ do_mix_chainping($addr1, $type1, $key1, $addr2, $type2, $key2, $to, $body);
+ } elsif ($chaintype eq 'cpunk') {
+ do_cpunk_chainping($addr1, $type1, $key1, $addr2, $type2, $key2, $to, $body);
+ } else {
+ Echolot::Log::warn("Don't know how to handle chain ping type $chaintype.");
+ return 0;
+ };
+
+ Echolot::Globals::get()->{'storage'}->register_chainpingout($chaintype, $addr1, $type1, $key1, $addr2, $type2, $key2, $now);
+ return 1;
+};
+
+sub send_pings($;$$) {
+ return 1 unless Echolot::Config::get()->{'do_chainpings'};
+
+ my ($scheduled_for, $which1, $which2) = @_;
+
+ $which1 = '' unless defined $which1;
+ $which2 = '' unless defined $which2;
+
+ my $call_intervall = Echolot::Config::get()->{'chainpinger_interval'};
+ my $send_every_n_calls = Echolot::Config::get()->{'chainping_every_nth_time'};
+
+ my $timemod = int ($scheduled_for / $call_intervall);
+ my $this_call_id = $timemod % $send_every_n_calls;
+ my $session_id = int ($scheduled_for / ($call_intervall * $send_every_n_calls));
+
+ # Same thing for Intensive Care -- yet unknown or already broken chains
+ my $send_every_n_calls_ic = Echolot::Config::get()->{'chainping_ic_every_nth_time'};
+
+ my $timemod_ic = int ($scheduled_for / $call_intervall);
+ my $this_call_id_ic = $timemod_ic % $send_every_n_calls_ic;
+ my $session_id_ic = int ($scheduled_for / ($call_intervall * $send_every_n_calls_ic));
+
+ my @remailers = Echolot::Globals::get()->{'storage'}->get_addresses();
+ for my $chaintype (keys %{Echolot::Config::get()->{'which_chainpings'}}) {
+
+ my @thisrems;
+ for my $rem (@remailers) {
+ next unless $rem->{'pingit'};
+ my $addr = $rem->{'address'};
+ my $type;
+ my %supports = map { $_ => 1 } Echolot::Globals::get()->{'storage'}->get_types($addr);
+ for my $thistype (@{Echolot::Config::get()->{'which_chainpings'}->{$chaintype}}) {
+ $type = $thistype, last if $supports{$thistype};
+ };
+ next unless $type;
+ my $key;
+ my $latest = 0;
+ for my $keyid (Echolot::Globals::get()->{'storage'}->get_keys($addr, $type)) {
+ my %key = Echolot::Globals::get()->{'storage'}->get_key($addr, $type, $keyid);
+ $key = $keyid, $latest = $key{'last_update'} if $latest < $key{'last_update'};
+ };
+ push @thisrems, { addr => $addr, type => $type, key => $key };
+ };
+
+ for my $rem1 (@thisrems) {
+ my $addr1 = $rem1->{'addr'};
+
+ next unless (
+ $which1 eq 'all' ||
+ $which1 eq $addr1 ||
+ $which1 eq '');
+
+ my $type1 = $rem1->{'type'};
+ my $key1 = $rem1->{'key'};
+
+ for my $rem2 (@thisrems) {
+ my $addr2 = $rem2->{'addr'};
+ next if $rem1 eq $rem2 && (! ($which1 eq $addr2 && $which2 eq $addr2));
+
+ next unless (
+ $which2 eq 'all' ||
+ $which2 eq $addr2 ||
+ $which2 eq '');
+
+ my $type2 = $rem2->{'type'};
+ my $key2 = $rem2->{'key'};
+
+ my $call_id = Echolot::Tools::makeShortNumHash($addr1.$addr2.$chaintype.$session_id ) % $send_every_n_calls;
+ my $call_id_ic = Echolot::Tools::makeShortNumHash($addr1.$addr2.$chaintype.$session_id_ic) % $send_every_n_calls_ic;
+ next unless (
+ (($which1 eq $addr1 || $which1 eq 'all' ) && ($which2 eq $addr2 || $which2 eq 'all')) ||
+ (($which1 eq '' && $which2 eq '') && (
+ $this_call_id eq $call_id ||
+ (defined $INTENSIVE_CARE{$chaintype}->{$addr1.' '.$addr2} && $this_call_id_ic eq $call_id_ic))));
+
+ Echolot::Log::debug("chainping calling $chaintype, $addr1 ($type1, $key1) - $addr2 ($type2, $key2)");
+ do_chainping($chaintype, $addr1, $type1, $key1, $addr2, $type2, $key2);
+ };
+ };
+ };
+ return 1;
+};
+
+sub set_intensive_care($@) {
+ my ($chaintype, $intensive_care) = @_;
+
+ %{$INTENSIVE_CARE{$chaintype}} = map { ($_->{'addr1'}.' '.$_->{'addr2'}) => $_->{'reason'} } @$intensive_care;
+ if (scalar @$intensive_care) {
+ Echolot::Log::debug("intensive care $chaintype:\n" . join("\n", sort { $a cmp $b } map { "$_: $INTENSIVE_CARE{$chaintype}->{$_}" } keys %{$INTENSIVE_CARE{$chaintype}} ));
+ } else {
+ Echolot::Log::debug("intensive care $chaintype: (none)");
+ };
+};
+
+sub receive($$$$) {
+ my ($header, $msg, $token, $timestamp) = @_;
+
+ my $now = time();
+
+ my $body;
+ if ($msg =~ /^-----BEGIN PGP MESSAGE-----/m) {
+ # work around borken middleman remailers that have a problem with some
+ # sort of end of line characters and randhopping them through reliable
+ # remailers..
+ # they add an empty line between each usefull line
+ $msg =~ s/(\r?\n)\r?\n/$1/g if ($msg =~ /^-----BEGIN PGP MESSAGE-----\r?\n\r?\n/m);
+ $body = Echolot::Tools::crypt_symmetrically($msg, 'decrypt');
+ };
+ $body = $msg unless defined $body;
+
+ my ($chaintype) = $body =~ /^chaintype: (.*)$/m;
+ my ($addr1) = $body =~ /^remailer1: (.*)$/m;
+ my ($type1) = $body =~ /^type1: (.*)$/m;
+ my ($key1) = $body =~ /^key1: (.*)$/m;
+ my ($addr2) = $body =~ /^remailer2: (.*)$/m;
+ my ($type2) = $body =~ /^type2: (.*)$/m;
+ my ($key2) = $body =~ /^key2: (.*)$/m;
+ my ($sent) = $body =~ /^sent: (.*)$/m;
+ my ($mac) = $body =~ /^mac: (.*)$/m;
+
+ my @values = ($chaintype, $addr1, $type1, $key1, $addr2, $type2, $key2, $sent, $mac);
+ my $cleanstring = join ":", map { defined() ? $_ : "undef" } @values;
+
+ (grep { ! defined() } @values) and
+ Echolot::Log::warn("Received chainping at $timestamp has undefined values: $cleanstring."),
+ return 0;
+
+ pop @values;
+ Echolot::Tools::verify_mac(join(':', @values), $mac) or
+ Echolot::Log::warn("Received chainping at $timestamp has wrong mac; $cleanstring."),
+ return 0;
+
+ Echolot::Globals::get()->{'storage'}->register_chainpingdone($chaintype, $addr1, $type1, $key1, $addr2, $type2, $key2, $sent, $now - $sent) or
+ return 0;
+
+ return 1;
+};
+
+1;
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Commands.pm b/trunk/Echolot/Commands.pm
new file mode 100644
index 0000000..0602bf2
--- /dev/null
+++ b/trunk/Echolot/Commands.pm
@@ -0,0 +1,131 @@
+package Echolot::Commands;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Commands - manage commands like add key, set ttl etc.
+
+=head1 DESCRIPTION
+
+This package provides functions for sending out and receiving pings.
+
+=cut
+
+use strict;
+use Echolot::Log;
+use Fcntl ':flock'; # import LOCK_* constants
+#use Fcntl ':seek'; # import SEEK_* constants
+use POSIX; # import SEEK_* constants (older perls don't have SEEK_ in Fcntl)
+use English;
+
+sub addCommand($) {
+ my ($command) = @_;
+
+ my $filename = Echolot::Config::get()->{'commands_file'};
+ open(FH, ">>$filename" ) or
+ Echolot::Log::warn("Cannot open $filename for appending $!."),
+ return 0;
+ flock(FH, LOCK_EX) or
+ Echolot::Log::warn("Cannot get exclusive lock on $filename: $!."),
+ return 0;
+
+ print FH $command,"\n";
+
+ flock(FH, LOCK_UN) or
+ Echolot::Log::warn("Cannot unlock $filename: $!.");
+ close(FH) or
+ Echolot::Log::warn("Cannot close $filename: $!.");
+};
+
+sub processCommands($) {
+ my $filename = Echolot::Config::get()->{'commands_file'};
+
+ (-e $filename) or
+ return 1;
+ my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks)= stat $filename;
+ ($size > 0) or
+ return 1;
+
+ open(FH, "+<$filename" ) or
+ Echolot::Log::warn("Cannot open $filename for reading: $!."),
+ return 0;
+ flock(FH, LOCK_EX) or
+ Echolot::Log::warn("Cannot get exclusive lock on $filename: $!."),
+ return 0;
+
+
+ while (<FH>) {
+ chomp;
+ my ($command, @args) = split;
+
+ if ($command eq 'add') {
+ Echolot::Globals::get()->{'storage'}->add_address(@args);
+ } elsif ($command eq 'set') {
+ Echolot::Globals::get()->{'storage'}->set_stuff(@args);
+ } elsif ($command eq 'getkeyconf') {
+ Echolot::Globals::get()->{'scheduler'}->schedule('getkeyconf', 0, time(), \@args );
+ } elsif ($command eq 'sendpings') {
+ Echolot::Globals::get()->{'scheduler'}->schedule('ping', 0, time(), \@args );
+ } elsif ($command eq 'sendchainpings') {
+ Echolot::Globals::get()->{'scheduler'}->schedule('chainping', 0, time(), \@args );
+ } elsif ($command eq 'buildstats') {
+ Echolot::Globals::get()->{'scheduler'}->schedule('buildstats', 0, time() );
+ } elsif ($command eq 'buildkeys') {
+ Echolot::Globals::get()->{'scheduler'}->schedule('buildkeys', 0, time() );
+ } elsif ($command eq 'buildthesaurus') {
+ Echolot::Globals::get()->{'scheduler'}->schedule('buildthesaurus', 0, time() );
+ } elsif ($command eq 'buildfromlines') {
+ Echolot::Globals::get()->{'scheduler'}->schedule('buildfromlines', 0, time() );
+ } elsif ($command eq 'summary') {
+ @args = ('manual');
+ Echolot::Globals::get()->{'scheduler'}->schedule('summary', 0, time(), \@args );
+ } elsif ($command eq 'delete') {
+ Echolot::Globals::get()->{'storage'}->delete_remailer(@args);
+ } elsif ($command eq 'setremailercaps') {
+ my $addr = shift @args;
+ my $conf = join(' ', @args);
+ Echolot::Conf::set_caps_manually($addr, $conf);
+ } elsif ($command eq 'deleteremailercaps') {
+ Echolot::Globals::get()->{'storage'}->delete_remailercaps(@args);
+ } else {
+ Echolot::Log::warn("Unkown command: '$_'.");
+ };
+ };
+
+ seek(FH, 0, SEEK_SET) or
+ Echolot::Log::warn("Cannot seek to start '$filename': $!."),
+ return 0;
+ truncate(FH, 0) or
+ Echolot::Log::warn("Cannot truncate '$filename' to zero length: $!."),
+ return 0;
+ flock(FH, LOCK_UN) or
+ Echolot::Log::warn("Cannot unlock '$filename': $!.");
+ close(FH) or
+ Echolot::Log::warn("Cannot close '$filename': $!.");
+};
+
+1;
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Conf.pm b/trunk/Echolot/Conf.pm
new file mode 100644
index 0000000..2bfa582
--- /dev/null
+++ b/trunk/Echolot/Conf.pm
@@ -0,0 +1,531 @@
+package Echolot::Conf;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Conf - remailer Configuration/Capabilities
+
+=head1 DESCRIPTION
+
+This package provides functions for requesting, parsing, and analyzing
+remailer-conf and remailer-key replies.
+
+=head1 CAVEATS
+
+When parsing OpenPGP keys only the address of the primary user id is taken into
+account (This is the one with the latest self signature I think).
+
+=cut
+
+use strict;
+use Echolot::Log;
+use GnuPG::Interface;
+
+
+sub is_not_a_remailer($) {
+ my ($reply) = @_;
+ if ($reply =~ /^\s* not \s+ a \s+ remailer\b/xi) {
+ return 1;
+ } else {
+ return 0;
+ };
+};
+
+sub send_requests($;$) {
+ my ($scheduled_for, $which) = @_;
+
+ $which = '' unless defined $which;
+
+ my $call_intervall = Echolot::Config::get()->{'getkeyconf_interval'};
+ my $send_every_n_calls = Echolot::Config::get()->{'getkeyconf_every_nth_time'};
+
+ my $timemod = int ($scheduled_for / $call_intervall);
+ my $this_call_id = $timemod % $send_every_n_calls;
+ my $session_id = int ($scheduled_for / ($call_intervall * $send_every_n_calls));
+
+ Echolot::Globals::get()->{'storage'}->delay_commit();
+
+ for my $remailer (Echolot::Globals::get()->{'storage'}->get_addresses()) {
+ next unless ($remailer->{'status'} eq 'active');
+ next unless ($remailer->{'fetch'});
+ my $address = $remailer->{'address'};
+
+ next unless (
+ $which eq 'all' ||
+ $which eq $address ||
+ $which eq '');
+
+ for my $type (qw{conf key help stats adminkey}) {
+
+ next unless (
+ $which eq $address ||
+ $which eq 'all' ||
+ (($which eq '') && ($this_call_id == (Echolot::Tools::makeShortNumHash($address.$type.$session_id) % $send_every_n_calls))));
+
+ Echolot::Log::debug("Sending $type request to ".$address.".");
+
+ my $source_text = Echolot::Config::get()->{'remailerxxxtext'};
+ my $template = HTML::Template->new(
+ scalarref => \$source_text,
+ strict => 0,
+ global_vars => 1 );
+ $template->param ( address => $address );
+ $template->param ( operator_address => Echolot::Config::get()->{'operator_address'} );
+ my $body = $template->output();
+
+ Echolot::Tools::send_message(
+ 'To' => $address,
+ 'Subject' => 'remailer-'.$type,
+ 'Token' => $type.'.'.$remailer->{'id'},
+ 'Body' => $body);
+
+ Echolot::Globals::get()->{'storage'}->decrease_ttl($address) if (($type eq 'conf') && ($which eq ''));
+ };
+ };
+ Echolot::Globals::get()->{'storage'}->enable_commit();
+};
+
+sub check_resurrection() {
+ Echolot::Globals::get()->{'storage'}->delay_commit();
+ for my $remailer (Echolot::Globals::get()->{'storage'}->get_addresses()) {
+ next unless ($remailer->{'status'} eq 'ttl timeout');
+ next unless ($remailer->{'fetch'});
+ next unless ($remailer->{'resurrection_ttl'});
+ Echolot::Log::debug("Sending request to ".$remailer->{'address'}." to check for resurrection.");
+ for my $type (qw{conf key help stats adminkey}) {
+ Echolot::Tools::send_message(
+ 'To' => $remailer->{'address'},
+ 'Subject' => 'remailer-'.$type,
+ 'Token' => $type.'.'.$remailer->{'id'})
+ };
+ Echolot::Globals::get()->{'storage'}->decrease_resurrection_ttl($remailer->{'address'});
+ };
+ Echolot::Globals::get()->{'storage'}->enable_commit();
+};
+
+
+sub remailer_caps($$$;$) {
+ my ($conf, $token, $time, $dontexpire) = @_;
+
+ my ($id) = $token =~ /^conf\.(\d+)$/;
+ (defined $id) or
+ Echolot::Log::info("Returned token '$token' has no id at all."),
+ return 0;
+
+ Echolot::Log::info("Could not find id in token '$token'."), return 0 unless defined $id;
+ my ($remailer_type) = ($conf =~ /^\s*Remailer-Type:\s* (.*?) \s*$/imx);
+ Echolot::Log::info("No remailer type found in remailer_caps from '$token'."), return 0 unless defined $remailer_type;
+ my ($remailer_caps) = ($conf =~ /^\s*( \$remailer{".*"} \s*=\s* "<.*@.*>.*"; )\s*$/imx);
+ Echolot::Log::info("No remailer caps found in remailer_caps from '$token'."), return 0 unless defined $remailer_caps;
+ my ($remailer_nick, $remailer_address) = ($remailer_caps =~ /^\s* \$remailer{"(.*)"} \s*=\s* "<(.*@.*)>.*"; \s*$/ix);
+ Echolot::Log::info("No remailer nick found in remailer_caps from '$token': '$remailer_caps'."), return 0 unless defined $remailer_nick;
+ Echolot::Log::info("No remailer address found in remailer_caps from '$token': '$remailer_caps'."), return 0 unless defined $remailer_address;
+
+
+ my $remailer = Echolot::Globals::get()->{'storage'}->get_address_by_id($id);
+ Echolot::Log::info("No remailer found for id '$id'."), return 0 unless defined $remailer;
+ if ($remailer->{'address'} ne $remailer_address) {
+ # Address mismatch -> Ignore reply and add $remailer_address to prospective addresses
+ Echolot::Log::info("Remailer address mismatch $remailer->{'address'} vs $remailer_address. Adding latter to prospective remailers.");
+ Echolot::Globals::get()->{'storage'}->add_prospective_address($remailer_address, 'self-capsstring-conf', $remailer_address);
+ } else {
+ Echolot::Log::debug("Setting capabilities for $remailer_address");
+ Echolot::Globals::get()->{'storage'}->restore_ttl( $remailer->{'address'} );
+ Echolot::Globals::get()->{'storage'}->set_caps($remailer_type, $remailer_caps, $remailer_nick, $remailer_address, $time, $dontexpire);
+
+ # if remailer is cpunk and not pgponly
+ if (($remailer_caps =~ /\bcpunk\b/) && !($remailer_caps =~ /\bpgponly\b/)) {
+ Echolot::Globals::get()->{'storage'}->set_key(
+ 'cpunk-clear',
+ $remailer_nick,
+ $remailer->{'address'},
+ 'N/A',
+ 'none',
+ 'N/A',
+ 'N/A',
+ 'N/A',
+ $time);
+ }
+ }
+
+
+ # Fetch prospective remailers from reliable's remailer-conf reply:
+ my @lines = split /\r?\n/, $conf;
+
+ while (1) {
+ my $head;
+ while (@lines) {
+ $head = $lines[0];
+ chomp $head;
+ shift @lines;
+ last if ($head eq 'SUPPORTED CPUNK (TYPE I) REMAILERS' ||
+ $head eq 'SUPPORTED MIXMASTER (TYPE II) REMAILERS');
+ };
+ last unless defined $head;
+ my $wanting = $head eq 'SUPPORTED CPUNK (TYPE I) REMAILERS' ? 1 :
+ $head eq 'SUPPORTED MIXMASTER (TYPE II) REMAILERS' ? 2 :
+ undef;
+ last unless defined $wanting;
+
+ while (@lines) {
+ $head = $lines[0];
+ chomp $head;
+ shift @lines;
+ if ($wanting == 1) {
+ last unless ($head =~ /<(.*?@.*?)>/);
+ Echolot::Globals::get()->{'storage'}->add_prospective_address($1, 'reliable-caps-reply-type1', $remailer_address);
+ } elsif ($wanting == 2) {
+ last unless ($head =~ /\s(.*?@.*?)\s/);
+ Echolot::Globals::get()->{'storage'}->add_prospective_address($1, 'reliable-caps-reply-type2', $remailer_address);
+ } else {
+ Echolot::Log::confess("Shouldn't be here. wanting == $wanting.");
+ };
+ };
+ };
+
+ return 1;
+};
+
+sub remailer_conf($$$) {
+ my ($reply, $token, $time) = @_;
+
+ my ($id) = $token =~ /^conf\.(\d+)$/;
+ (defined $id) or
+ Echolot::Log::info ("Returned token '$token' has no id at all."),
+ return 0;
+
+ my $remailer = Echolot::Globals::get()->{'storage'}->get_address_by_id($id);
+ Echolot::Log::info("No remailer found for id '$id'."), return 0 unless defined $remailer;
+ Echolot::Log::debug("Received remailer-conf reply for $remailer."),
+
+ Echolot::Globals::get()->{'storage'}->not_a_remailer($id), return 1
+ if (is_not_a_remailer($reply));
+ Echolot::Thesaurus::save_thesaurus('conf', $id, $reply);
+
+
+ remailer_caps($reply, $token, $time);
+};
+
+sub set_caps_manually($$) {
+ my ($addr, $caps) = @_;
+
+ defined $addr or
+ Echolot::Log::info("Address not defined."),
+ return 0;
+ defined $caps or
+ Echolot::Log::info("Caps not defined."),
+ return 0;
+
+ Echolot::Log::info("Setting caps for $addr manually to $caps.");
+
+ my $remailer = Echolot::Globals::get()->{'storage'}->get_address($addr);
+ defined $remailer or
+ Echolot::Log::info("Remailer address $addr did not give a valid remailer."),
+ return 0;
+ my $id = $remailer->{'id'};
+ defined $id or
+ Echolot::Log::info("Remailer address $addr did not give a remailer with an id."),
+ return 0;
+ my $token = 'conf.'.$id;
+
+ my $conf = "Remailer-Type: set-manually\n$caps";
+ remailer_caps($conf, $token, time, 1);
+
+ return 1;
+};
+
+sub parse_mix_key($$$) {
+ my ($reply, $time, $remailer) = @_;
+
+# -----Begin Mix Key-----
+# 7f6d997678b19ccac110f6e669143126
+# 258
+# AASyedeKiP1/UKyfrBz2K6gIhv4jfXIaHo8dGmwD
+# KqkG3DwytgSySSY3wYm0foT7KvEnkG2aTi/uJva/
+# gymE+tsuM8l8iY1FOiXwHWLDdyUBPbrLjRkgm7GD
+# Y7ogSjPhVLeMpzkSyO/ryeUfLZskBUBL0LxjLInB
+# YBR3o6p/RiT0EQAAAAAAAAAAAAAAAAAAAAAAAAAA
+# AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+# AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+# AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+# AAAAAAAAAAAAAAAAAAAAAQAB
+# -----End Mix Key-----
+
+ my %mixmasters;
+ # rot26 rot26@mix.uucico.de 7f6d997678b19ccac110f6e669143126 2.9b33 MC
+ my @mix_confs = ($reply =~ /^
+ [a-z0-9]+
+ \s+
+ \S+\@\S+
+ \s+
+ [0-9a-f]{32}
+ .*?$/xmg);
+ my @mix_keys = ($reply =~ /^-----Begin \s Mix \s Key-----\r?\n
+ [0-9a-f]{32}\r?\n
+ \d+\r?\n
+ (?:[a-zA-Z0-9+\/]*\r?\n)+
+ -----End \s Mix \s Key-----$/xmg );
+ for (@mix_confs) {
+ my ($nick, $address, $keyid, $version, $caps, $created, $expires) = /^
+ ([a-z0-9]+)
+ \s+
+ (\S+@\S+)
+ \s+
+ ([0-9a-f]{32})
+ (?: [ \t]+
+ (\S+)
+ (?: [ \t]+
+ (\S+)
+ (?: [ \t]+
+ (\d{4}-\d{2}-\d{2})
+ (?: [ \t]+
+ (\d{4}-\d{2}-\d{2})
+ )?
+ )?
+ )?
+ )? .*?/x;
+ $mixmasters{$keyid} = {
+ nick => $nick,
+ address => $address,
+ version => $version,
+ caps => $caps,
+ created => $created,
+ expires => $expires,
+ summary => $_
+ };
+ };
+ for (@mix_keys) {
+ my ($keyid) = /^-----Begin \s Mix \s Key-----\r?\n
+ ([0-9a-f]{32})\r?\n
+ \d+\r?\n
+ (?:[a-zA-Z0-9+\/]*\r?\n)+
+ -----End \s Mix \s Key-----$/xmg;
+ $mixmasters{$keyid}->{'key'} = $_;
+ };
+
+ for my $keyid (keys %mixmasters) {
+ my $remailer_address = $mixmasters{$keyid}->{'address'};
+ (defined $mixmasters{$keyid}->{'nick'}) or
+ Echolot::Log::info("Could not parse a remailer-key reply."),
+ next;
+ (defined $mixmasters{$keyid}->{'nick'} && ! defined $mixmasters{$keyid}->{'key'}) and
+ Echolot::Log::info("Mixmaster key header without key in reply from $remailer_address."),
+ next;
+ (! defined $mixmasters{$keyid}->{'nick'} && defined $mixmasters{$keyid}->{'key'}) and
+ Echolot::Log::info("Mixmaster key without key header in reply from $remailer_address."),
+ next;
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime();
+ my $today = sprintf("%04d-%02d-%02d", $year+1900, $mon+1, $mday);
+ (defined $mixmasters{$keyid}->{'created'} && ($today lt $mixmasters{$keyid}->{'created'})) and
+ Echolot::Log::info("Mixmaster key for $remailer_address created in the future ($today < ".$mixmasters{$keyid}->{'created'}.")."),
+ next;
+ (defined $mixmasters{$keyid}->{'expires'} && ($mixmasters{$keyid}->{'expires'} lt $today)) and
+ Echolot::Log::info("Mixmaster key for $remailer_address expired (".$mixmasters{$keyid}->{'expires'}." < $today)."),
+ next;
+
+ if ($remailer->{'address'} ne $remailer_address) {
+ # Address mismatch -> Ignore reply and add $remailer_address to prospective addresses
+ Echolot::Log::info("Remailer address mismatch $remailer->{'address'} vs $remailer_address. Adding latter to prospective remailers.");
+ Echolot::Globals::get()->{'storage'}->add_prospective_address($remailer_address, 'self-capsstring-key', $remailer_address);
+ } else {
+ Echolot::Log::debug("Setting mix key for $remailer_address: $keyid");
+ Echolot::Globals::get()->{'storage'}->restore_ttl( $remailer->{'address'} );
+ Echolot::Globals::get()->{'storage'}->set_key(
+ 'mix',
+ $mixmasters{$keyid}->{'nick'},
+ $mixmasters{$keyid}->{'address'},
+ $mixmasters{$keyid}->{'key'},
+ $keyid,
+ $mixmasters{$keyid}->{'version'},
+ $mixmasters{$keyid}->{'caps'},
+ $mixmasters{$keyid}->{'summary'},
+ $time);
+ }
+ };
+
+ return 1;
+};
+
+sub parse_cpunk_key($$$) {
+ my ($reply, $time, $remailer) = @_;
+
+ my $GnuPG = new GnuPG::Interface;
+ $GnuPG->call( Echolot::Config::get()->{'gnupg'} ) if (Echolot::Config::get()->{'gnupg'});
+ $GnuPG->options->hash_init(
+ homedir => Echolot::Config::get()->{'gnupghome'} );
+ $GnuPG->options->meta_interactive( 0 );
+ my %cypherpunk;
+
+ my @pgp_keys = ($reply =~ /^-----BEGIN \s PGP \s PUBLIC \s KEY \s BLOCK-----\r?\n
+ (?:.+\r?\n)*
+ \r?\n
+ (?:[a-zA-Z0-9+\/=]*\r?\n)+
+ -----END \s PGP \s PUBLIC \s KEY \s BLOCK-----$/xmg );
+ for my $key (@pgp_keys) {
+ my ( $stdin_fh, $stdout_fh, $stderr_fh, $status_fh, $handles ) = Echolot::Tools::make_gpg_fds();
+ my $pid = $GnuPG->wrap_call(
+ commands => [qw{--with-colons}],
+ command_args => [qw{--no-options --no-secmem-warning --no-default-keyring --fast-list-mode}],
+ handles => $handles );
+ my ($stdout, $stderr, $status) = Echolot::Tools::readwrite_gpg($key, $stdin_fh, $stdout_fh, $stderr_fh, $status_fh);
+ waitpid $pid, 0;
+
+ ($stderr eq '') or
+ Echolot::Log::info("GnuPG returned something in stderr: '$stderr' when checking key '$key'; So what?");
+ ($status eq '') or
+ Echolot::Log::info("GnuPG returned something in status '$status' when checking key '$key': So what?");
+
+ my @included_keys = $stdout =~ /^pub:.*$/mg;
+ (scalar @included_keys >= 2) &&
+ # FIXME handle more than one key per block nicely
+ Echolot::Log::debug ("Cannot handle more than one key per block nicely (correctly) yet. Found ".(scalar @included_keys)." in one block from ".$remailer->{'address'}.".");
+ for my $included_key (@included_keys) {
+ my ($type, $keyid, $uid) = $included_key =~ /pub::\d+:(\d+):([0-9A-F]+):[^:]+:[^:]*:::([^:]+):/;
+ (defined $uid) or
+ Echolot::Log::info ("Unexpected format of '$included_key' by ".$remailer->{'address'}."; Skipping."),
+ next;
+ my ($address) = $uid =~ /<(.*?)>/;
+ $cypherpunk{$keyid} = {
+ address => $address,
+ type => $type,
+ key => $key # FIXME handle more than one key per block correctly
+ };
+ };
+ };
+
+ for my $keyid (keys %cypherpunk) {
+ my $remailer_address = $cypherpunk{$keyid}->{'address'};
+
+ if ($remailer->{'address'} ne $remailer_address) {
+ # Address mismatch -> Ignore reply and add $remailer_address to prospective addresses
+ Echolot::Log::info("Remailer address mismatch $remailer->{'address'} vs $remailer_address id key $keyid. Adding latter to prospective remailers.");
+ Echolot::Globals::get()->{'storage'}->add_prospective_address($remailer_address, 'self-capsstring-key', $remailer_address);
+ } else {
+ Echolot::Globals::get()->{'storage'}->restore_ttl( $remailer->{'address'} );
+ # 1 .. RSA
+ # 17 .. DSA
+ if ($cypherpunk{$keyid}->{'type'} == 1 || $cypherpunk{$keyid}->{'type'} == 17 ) {
+ Echolot::Log::debug("Setting cpunk key for $remailer_address: $keyid; type ".$cypherpunk{$keyid}->{'type'});
+ Echolot::Globals::get()->{'storage'}->set_key(
+ (($cypherpunk{$keyid}->{'type'} == 1) ? 'cpunk-rsa' :
+ (($cypherpunk{$keyid}->{'type'} == 17) ? 'cpunk-dsa' :
+ 'ERROR')),
+ $keyid, # as nick
+ $cypherpunk{$keyid}->{'address'},
+ $cypherpunk{$keyid}->{'key'},
+ $keyid,
+ 'N/A',
+ 'N/A',
+ 'N/A',
+ $time);
+ } else {
+ Echolot::Log::info("$keyid from $remailer_address has algoid ".$cypherpunk{$keyid}->{'type'}.". Cannot handle those.");
+ };
+ }
+ };
+
+ return 1;
+};
+
+sub remailer_key($$$) {
+ my ($reply, $token, $time) = @_;
+
+ my $cp_reply = $reply;
+ $cp_reply =~ s/^- -/-/gm; # PGP Signed messages
+
+ my ($id) = $token =~ /^key\.(\d+)$/;
+ (defined $id) or
+ Echolot::Log::info ("Returned token '$token' has no id at all."),
+ return 0;
+
+ my $remailer = Echolot::Globals::get()->{'storage'}->get_address_by_id($id);
+ Echolot::Log::info("No remailer found for id '$id'."), return 0 unless defined $remailer;
+ Echolot::Log::debug("Received remailer-keys reply for $remailer."),
+
+ Echolot::Globals::get()->{'storage'}->not_a_remailer($id), return 1
+ if (is_not_a_remailer($reply));
+ Echolot::Thesaurus::save_thesaurus('key', $id, $reply);
+
+ parse_mix_key($cp_reply, $time, $remailer);
+ parse_cpunk_key($cp_reply, $time, $remailer);
+
+ return 1;
+};
+
+sub remailer_stats($$$) {
+ my ($reply, $token, $time) = @_;
+
+ my ($id) = $token =~ /^stats\.(\d+)$/;
+ (defined $id) or
+ Echolot::Log::info ("Returned token '$token' has no id at all."),
+ return 0;
+
+
+ my $remailer = Echolot::Globals::get()->{'storage'}->get_address_by_id($id);
+ Echolot::Log::info("No remailer found for id '$id'."), return 0 unless defined $remailer;
+ Echolot::Log::debug("Received remailer-stats reply for $remailer."),
+
+ Echolot::Globals::get()->{'storage'}->not_a_remailer($id), return 1
+ if (is_not_a_remailer($reply));
+ Echolot::Thesaurus::save_thesaurus('stats', $id, $reply);
+};
+
+sub remailer_help($$$) {
+ my ($reply, $token, $time) = @_;
+
+ my ($id) = $token =~ /^help\.(\d+)$/;
+ (defined $id) or
+ Echolot::Log::info ("Returned token '$token' has no id at all."),
+ return 0;
+
+ my $remailer = Echolot::Globals::get()->{'storage'}->get_address_by_id($id);
+ Echolot::Log::info("No remailer found for id '$id'."), return 0 unless defined $remailer;
+ Echolot::Log::debug("Received remailer-help reply for $remailer."),
+
+ Echolot::Globals::get()->{'storage'}->not_a_remailer($id), return 1
+ if (is_not_a_remailer($reply));
+ Echolot::Thesaurus::save_thesaurus('help', $id, $reply);
+};
+
+sub remailer_adminkey($$$) {
+ my ($reply, $token, $time) = @_;
+
+ my ($id) = $token =~ /^adminkey\.(\d+)$/;
+ (defined $id) or
+ Echolot::Log::info ("Returned token '$token' has no id at all."),
+ return 0;
+
+ my $remailer = Echolot::Globals::get()->{'storage'}->get_address_by_id($id);
+ Echolot::Log::info("No remailer found for id '$id'."), return 0 unless defined $remailer;
+ Echolot::Log::debug("Received remailer-adminkey reply for $remailer."),
+
+ Echolot::Globals::get()->{'storage'}->not_a_remailer($id), return 1
+ if (is_not_a_remailer($reply));
+ Echolot::Thesaurus::save_thesaurus('adminkey', $id, $reply);
+};
+
+1;
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Config.pm b/trunk/Echolot/Config.pm
new file mode 100644
index 0000000..dd3d97e
--- /dev/null
+++ b/trunk/Echolot/Config.pm
@@ -0,0 +1,344 @@
+package Echolot::Config;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Config - echolot configuration
+
+=head1 DESCRIPTION
+
+Sets default configuration options and
+reads configuration from the config file.
+
+=head1 FILES
+
+The configuration file is searched in those places in that order:
+
+=over
+
+=item the file pointed to by the B<ECHOLOT_CONF> environment variable
+
+=item <basedir>/pingd.conf
+
+=item $HOME/echolot/pingd.conf
+
+=item $HOME/pingd.conf
+
+=item $HOME/.pingd.conf
+
+=item /etc/echolot/pingd.conf
+
+=item /etc/pingd.conf
+
+=back
+
+=cut
+
+use strict;
+use Carp;
+use English;
+
+my $CONFIG;
+
+sub init($) {
+ my ($params) = @_;
+
+ die ("Basedir is not defined\n") unless defined $params->{'basedir'};
+
+ my @CONFIG_FILES = ();
+ push(@CONFIG_FILES, $ENV{'ECHOLOT_CONF'}) if defined $ENV{'ECHOLOT_CONF'};
+ push(@CONFIG_FILES, $params->{'basedir'}.'/pingd.conf') if defined $params->{'basedir'};
+ push(@CONFIG_FILES, $ENV{'HOME'}.'/echolot/pingd.conf') if defined $ENV{'HOME'};
+ push(@CONFIG_FILES, $ENV{'HOME'}.'/pingd.conf') if defined $ENV{'HOME'};
+ push(@CONFIG_FILES, $ENV{'HOME'}.'/.pingd.conf') if defined $ENV{'HOME'};
+ push(@CONFIG_FILES, '/etc/echolot/pingd.conf');
+ push(@CONFIG_FILES, '/etc/pingd.conf');
+
+ my $DEFAULT;
+ $DEFAULT = {
+ # System Specific Options
+ recipient_delimiter => '+',
+ dev_random => '/dev/random',
+ dev_urandom => '/dev/urandom',
+ sendmail => '/usr/sbin/sendmail',
+
+ # Magic Numbers
+ hash_len => 8,
+ stats_days => 12,
+ seconds_per_day => 24 * 60 * 60,
+
+ # New Remailers
+ fetch_new => 1,
+ ping_new => 1,
+ show_new => 1,
+
+ # Statistics Generation
+ separate_rlists => 0,
+ combined_list => 0,
+ thesaurus => 1,
+ fromlines => 1,
+ stats_sort_by_latency => 0,
+
+ # Timers and Counters
+ processmail => 60, # process incomng mail every minute
+ buildstats => 5*60, # build statistics every 5 minutes
+ buildkeys => 8*60*60, # build keyring every 8 hours
+ buildthesaurus => 60*60, # hourly
+ buildfromlines => 60*60, # hourly
+ commitprospectives => 8*60*60, # commit prospective addresses every 8 hours
+ expire => 24*60*60, # daily
+ getkeyconf_interval => 5*60, # send out requests every 5 minutes
+ getkeyconf_every_nth_time => 24*60/5, # send out the same request to the same remailer once a day
+ check_resurrection => 7*24*60*60, # weekly
+ summary => 24*60*60, # daily
+
+ metadata_backup => 8*60*60, # make backups of metadata and rotate them every 8 hours
+ metadata_backup_count => 32, # keep 32 rotations of metadata
+
+ pinger_interval => 5*60, # send out pings every 5 minutes
+ ping_every_nth_time => 24, # send out pings to the same remailer every 24 calls, i.e. every 2 hours
+
+ chainpinger_interval => 5*60, # send out pings every 5 minutes
+ chainping_every_nth_time => 2016, # send out pings to the same chain every 2016 calls, i.e. week
+ chainping_ic_every_nth_time => 288, # send out pings to broken or unknown chains every 288 calls, i.e. every day
+ chainping_period => 10*24*60*60, # 12 days
+ chainping_fudge => 0.3, # if less than 0.3 * rel1 * rel2 make it, the chain is really broken
+ chainping_grace => 1.5, # don't count pings sent no longer than 1.5 * (lat1 + lat2) ago
+ chainping_update => 4*60*60, # chain stats should never be older than 4 hours
+ chainping_minsample => 3, # have at least sent 3 pings before judging any chain
+ chainping_allbad_factor => 0.5, # at least 50% of possible chains (A x) need to fail for (A *) to be listed in broken chains
+
+ addresses_default_ttl => 5, # getkeyconf seconds (days)
+ check_resurrection_ttl => 8, # check_resurrection seconds (weeks)
+ prospective_addresses_ttl => 5*24*60*60, # 5 days
+ reliable_auto_add_min => 6, # 6 remailes need to list new address
+
+ expire_keys => 5*24*60*60, # 5 days
+ expire_confs => 5*24*60*60, # 5 days
+ expire_pings => 12*24*60*60, # 12 days
+ expire_thesaurus => 21*24*60*60, # 21 days
+ expire_chainpings => 12*24*60*60, # 12 days
+ expire_fromlines => 5*24*60*60, # 5 days
+ cleanup_tmpdir => 24*60*60, # daily
+
+ random_garbage => 8192,
+
+
+ # Directories and files
+ mailin => 'mail',
+ mailerrordir => 'mail-errors',
+ resultdir => 'results',
+ thesaurusdir => 'results/thesaurus',
+ thesaurusindexfile => 'results/thesaurus/index',
+ fromlinesindexfile => 'results/from',
+ private_resultdir => 'results.private',
+ indexfilebasename => 'echolot',
+ gnupghome => 'gnupghome',
+ gnupg => '',
+ mixhome => 'mixhome',
+ mixmaster => 'mix',
+ tmpdir => 'tmp',
+ broken1 => 'broken1.txt',
+ broken2 => 'broken2.txt',
+ sameop => 'sameop.txt',
+ gzip => 'gzip',
+
+ commands_file => 'commands.txt',
+ pidfile => 'pingd.pid',
+
+ save_errormails => 0,
+ write_meta_files => 1,
+ meta_extension => '.meta',
+
+ storage => {
+ backend => 'File',
+ File => {
+ basedir => 'data'
+ }
+ },
+
+ # logging
+ logfile => 'pingd.log',
+ loglevel => 'info',
+
+
+ # ping types
+ do_pings => {
+ 'cpunk-dsa' => 1,
+ 'cpunk-rsa' => 1,
+ 'cpunk-clear' => 1,
+ 'mix' => 1
+ },
+ do_chainpings => 1,
+ show_chainpings => 1,
+ which_chainpings => {
+ 'cpunk' => [ qw{cpunk-dsa cpunk-rsa cpunk-clear} ],
+ 'mix' => [ qw{mix} ]
+ },
+ pings_weight => [ qw{0.5 1.0 1.0 1.0 1.0 0.9 0.8 0.5 0.3 0.2 0.2 0.1 } ],
+
+ # templates
+ templates => {
+ default => {
+ 'indexfile' => 'templates/echolot.html',
+ 'thesaurusindexfile' => 'templates/thesaurusindex.html',
+ 'fromlinesindexfile' => 'templates/fromlinesindex.html',
+ 'mlist' => 'templates/mlist.html',
+ 'mlist2' => 'templates/mlist2.html',
+ 'rlist' => 'templates/rlist.html',
+ 'rlist-rsa' => 'templates/rlist-rsa.html',
+ 'rlist-dsa' => 'templates/rlist-dsa.html',
+ 'rlist-clear' => 'templates/rlist-clear.html',
+ 'rlist2' => 'templates/rlist2.html',
+ 'rlist2-rsa' => 'templates/rlist2-rsa.html',
+ 'rlist2-dsa' => 'templates/rlist2-dsa.html',
+ 'rlist2-clear' => 'templates/rlist2-clear.html',
+ 'clist' => 'templates/clist.html',
+ },
+ },
+
+ 'echolot_css' => 'templates/echolot.css',
+
+ remailerxxxtext => "Hello,\n".
+ "\n".
+ "This message requests remailer configuration data. The pinging software thinks\n".
+ "<TMPL_VAR NAME=\"address\"> is a remailer. Either it has been told so by the\n".
+ "maintainer of the pinger or it found the address in a remailer-conf or\n".
+ "remailer-key reply of some other remailer.\n".
+ "\n".
+ "If this is _not_ a remailer, you can tell this pinger that and it will stop\n".
+ "sending you those requests immediately (otherwise it will try a few more times).\n".
+ "Just reply and make sure the following is the first line of your message:\n".
+ " not a remailer\n".
+ "\n".
+ "If you want to talk to a human please mail <TMPL_VAR NAME=\"operator_address\">.\n",
+
+ homedir => undef,
+ my_localpart => undef,
+ my_domain => undef,
+ operator_address => undef,
+ sitename => undef,
+ verbose => 0
+ };
+
+
+ my $configfile = undef;
+ for my $filename ( @CONFIG_FILES ) {
+ if ( defined $filename && -e $filename ) {
+ $configfile = $filename;
+ print "Using config file $configfile\n" if ($params->{'verbose'});
+ last;
+ };
+ };
+
+ die ("no Configuration file found\n") unless defined $configfile;
+
+ {
+ local $/ = undef;
+ open(CONFIGCODE, $configfile) or
+ confess("Could not open configfile '$configfile': $!");
+ my $config_code = <CONFIGCODE>;
+ close (CONFIGCODE);
+ ($config_code) = $config_code =~ /^(.*)$/s;
+ eval ($config_code);
+ ($EVAL_ERROR) and
+ confess("Evaling config code from '$configfile' returned error: $EVAL_ERROR");
+ }
+
+
+ for my $key (keys %$CONFIG) {
+ warn("Unkown option: $key\n") unless (exists $DEFAULT->{$key});
+ };
+
+ # Work around spelling bug until 2.0rc3
+ if (exists $CONFIG->{'seperate_rlists'}) {
+ if (exists $CONFIG->{'separate_rlists'}) {
+ warn ("seperate_rlists has been superseded by separate_rlists.");
+ } else {
+ warn ("seperate_rlists has been superseded by separate_rlists, please change it in your config file.\n");
+ $CONFIG->{'separate_rlists'} = $CONFIG->{'seperate_rlists'};
+ };
+ delete $CONFIG->{'seperate_rlists'};
+ }
+
+ # In 2.0.6: thesaurusindexfile and indexfilebasename config values
+ # should not longer have the extension (.html) in them
+ # Handle this gracefully for now:
+ if (exists $CONFIG->{'thesaurusindexfile'}) {
+ $CONFIG->{'thesaurusindexfile'} =~ s/\.html?$// and
+ warn ("thesaurusindexfile no longer should have the .html extension.\n");
+ }
+ if (exists $CONFIG->{'indexfilebasename'}) {
+ $CONFIG->{'indexfilebasename'} =~ s/\.html?$// and
+ warn ("indexfilebasename no longer should have the .html extension.\n");
+ }
+
+ for my $key (keys %$DEFAULT) {
+ $CONFIG->{$key} = $DEFAULT->{$key} unless exists $CONFIG->{$key};
+ };
+ $CONFIG->{'homedir'} = $params->{'basedir'} unless (defined $CONFIG->{'homedir'});
+ $CONFIG->{'verbose'} = $params->{'verbose'} if ($params->{'verbose'});
+
+ for my $key (keys %$CONFIG) {
+ warn ("Config option $key is not defined\n") unless defined $CONFIG->{$key};
+ };
+};
+
+sub check_binaries() {
+ for my $bin (qw{mixmaster}) {
+ my $path = get()->{$bin};
+
+ if ($path =~ m#/#) {
+ Echolot::Log::warn ("$bin binary $path does not exist or is not executeable")
+ unless -x $path;
+ } else {
+ my $found = 0;
+ if (defined $ENV{'PATH'}) {
+ for my $pathelem (split /:/, $ENV{'PATH'}) {
+ $found = $pathelem, last
+ if -e $pathelem.'/'.$path;
+ };
+ };
+ if ($found) {
+ Echolot::Log::warn ("$bin binary $found/$path is not executeable")
+ unless -x $found.'/'.$path;
+ } else {
+ Echolot::Log::warn ("$bin binary $path not found");
+ };
+ };
+ };
+};
+
+sub get() {
+ return $CONFIG;
+};
+
+sub dump() {
+ print Data::Dumper->Dump( [ $CONFIG ], [ 'CONFIG' ] );
+};
+
+1;
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Fromlines.pm b/trunk/Echolot/Fromlines.pm
new file mode 100644
index 0000000..0479a42
--- /dev/null
+++ b/trunk/Echolot/Fromlines.pm
@@ -0,0 +1,126 @@
+package Echolot::Fromlines;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Thesaurus - build from header page
+
+=head1 DESCRIPTION
+
+This package builds the from header page with the information we
+received from pings.
+
+=cut
+
+use strict;
+use English;
+use Echolot::Log;
+
+
+sub build_fromlines() {
+ return 1 unless Echolot::Config::get()->{'fromlines'};
+
+ my $data;
+ my @remailers = Echolot::Globals::get()->{'storage'}->get_addresses();
+
+ for my $remailer (@remailers) {
+ next unless $remailer->{'showit'};
+ my $addr = $remailer->{'address'};
+ my $nick = Echolot::Globals::get()->{'storage'}->get_nick($addr);
+ next unless defined $nick;
+ my $caps = Echolot::Globals::get()->{'storage'}->get_capabilities($addr);
+ next unless defined $caps;
+ next unless $caps !~ m/\btesting\b/i;
+ my $middleman = $caps =~ m/\bmiddle\b/;
+ next if $middleman;
+
+
+ for my $user_supplied (0, 1) {
+ $data->{$user_supplied}->{$addr}->{'nick'} = $nick;
+ $data->{$user_supplied}->{$addr}->{'address'} = $addr;
+
+ my @types = Echolot::Globals::get()->{'storage'}->get_types($addr);
+ my $from_types;
+ for my $type (@types) {
+ my $from_info = Echolot::Globals::get()->{'storage'}->get_fromline($addr, $type, $user_supplied);
+ my $from = $from_info->{'from'};
+ $from = 'Not Available' unless defined $from;
+ $from = 'Middleman Remailer' if $middleman;
+ my $disclaim_top = $from_info->{'disclaim_top'} && ! $middleman ? 1 : 0;
+ my $disclaim_bot = $from_info->{'disclaim_bot'} && ! $middleman ? 1 : 0;
+ #my $last_update = $from_info->{'last_update'};
+ #my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($last_update);
+ my $frominfo = $disclaim_top.':'.$disclaim_bot.':'.$from;
+ #my $date = sprintf("%04d-%02d-%02d", $year+1900, $mon+1, $mday);
+ #my $value = $middleman ? $type : ($type." ($date)");
+ my $value = $type;
+ push @{$from_types->{$frominfo}}, $value;
+ };
+ my $types_from;
+ for my $frominfo (sort keys %$from_types) {
+ my $types = join ", ", sort { $a cmp $b } @{$from_types->{$frominfo}};
+ $types_from->{$types} = $frominfo;
+ };
+ my @types_from = map {
+ my ($disclaim_top, $disclaim_bot, $from) = split (/:/, $types_from->{$_}, 3);
+ {
+ nick => $nick,
+ address => $addr,
+ types => $_,
+ disclaim_top => $disclaim_top,
+ disclaim_bot => $disclaim_bot,
+ from => Echolot::Tools::escape_HTML_entities($from)
+ }
+ } sort { $a cmp $b } keys %$types_from;
+ $data->{$user_supplied}->{$addr}->{'data'} = \@types_from;
+ };
+
+ # Remove user supplied if identical
+ my $f0 = join ':', map {
+ $_->{'disclaim_top'}.':'.$_->{'disclaim_bot'}.$_->{'types'}.':'.$_->{'from'}
+ } @{$data->{0}->{$addr}->{'data'}};
+ my $f1 = join ':', map {
+ $_->{'disclaim_top'}.':'.$_->{'disclaim_bot'}.$_->{'types'}.':'.$_->{'from'}
+ } @{$data->{1}->{$addr}->{'data'}};
+ if ($f0 eq $f1) {
+ delete $data->{1}->{$addr};
+ };
+ };
+
+ my @data0 = map {$data->{0}->{$_}} (sort { $data->{0}->{$a}->{'nick'} cmp $data->{0}->{$b}->{'nick'} } keys (%{$data->{0}}));
+ my @data1 = map {$data->{1}->{$_}} (sort { $data->{1}->{$a}->{'nick'} cmp $data->{1}->{$b}->{'nick'} } keys (%{$data->{1}}));
+
+ Echolot::Tools::write_HTML_file(
+ Echolot::Config::get()->{'fromlinesindexfile'},
+ 'fromlinesindexfile',
+ Echolot::Config::get()->{'buildfromlines'},
+ default => \@data0,
+ usersupplied => \@data1);
+};
+
+
+1;
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Globals.pm b/trunk/Echolot/Globals.pm
new file mode 100644
index 0000000..898e5d6
--- /dev/null
+++ b/trunk/Echolot/Globals.pm
@@ -0,0 +1,60 @@
+package Echolot::Globals;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Globals - echolot global variables
+
+=head1 DESCRIPTION
+
+=cut
+
+use strict;
+use Carp;
+
+my $GLOBALS;
+
+sub init(%) {
+ my (%args) = @_;
+
+ my $hostname = `hostname`;
+ $hostname =~ /^([a-zA-Z0-9_.-]*)$/;
+ $hostname = $1 || 'unknown';
+ $GLOBALS->{'hostname'} = $hostname;
+ $GLOBALS->{'internalcounter'} = 1;
+ $GLOBALS->{'version'} = $args{'version'};
+};
+
+sub initStorage {
+ $GLOBALS->{'storage'} = new Echolot::Storage::File ( datadir => Echolot::Config::get()->{'storage'}->{'File'}->{'basedir'} );
+};
+
+sub get() {
+ return $GLOBALS;
+};
+
+1;
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Log.pm b/trunk/Echolot/Log.pm
new file mode 100644
index 0000000..15b5b80
--- /dev/null
+++ b/trunk/Echolot/Log.pm
@@ -0,0 +1,163 @@
+package Echolot::Log;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Globals - echolot global variables
+
+=head1 DESCRIPTION
+
+=cut
+
+use strict;
+use Carp qw{};
+#use Time::HiRes qw( gettimeofday );
+
+my %LOGLEVELS = qw{
+ trace 8
+ debug 7
+ info 6
+ notice 5
+ warn 4
+ warning 4
+ error 3
+ critical 2
+ alert 1
+ emergency 0
+};
+
+my $LOGLEVEL;
+my $LOGFILE;
+my $LOGFH;
+
+my @monnames = qw{Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec};
+sub header_log($$) {
+ my ($level, $msg) = @_;
+
+ #my ($secs, $msecs) = gettimeofday();
+ #my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime( $secs );
+ #my $time = sprintf("%s %02d %02d:%02d:%02d.%06d",
+ # $monnames[$mon],
+ # $mday,
+ # $hour, $min, $sec, $msecs);
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
+ my $time = sprintf("%s %02d %02d:%02d:%02d",
+ $monnames[$mon],
+ $mday,
+ $hour, $min, $sec);
+ my $prefix = $time.' ['.uc($level).'] ';
+ my $logstring = $msg."\n";
+ my $first = 0;
+ $logstring =~ s/^/ $prefix . ($first++ ? ' ' : '' ) /emg;
+ return $logstring;
+};
+
+sub reopen() {
+ $LOGFH->close() if ($LOGFH->opened());
+
+ open($LOGFH, ">>".$LOGFILE) or
+ warn("Cannot open logfile $LOGFILE: $!");
+};
+
+sub init() {
+ $LOGFILE = Echolot::Config::get()->{'logfile'};
+ $LOGLEVEL = Echolot::Config::get()->{'loglevel'};
+ $LOGFH = new IO::Handle;
+
+ die ("Logfile not defined") unless defined ($LOGFILE);
+ die ("Loglevel not defined") unless defined ($LOGLEVEL);
+ die ("Loglevel $LOGLEVEL unkown") unless defined ($LOGLEVELS{$LOGLEVEL});
+
+ $LOGLEVEL = $LOGLEVELS{$LOGLEVEL};
+
+ reopen();
+};
+
+sub log_message($$) {
+ my ($level, $msg) = @_;
+
+ die("Loglevel $level unkown.") unless defined $LOGLEVELS{$level};
+ return if $LOGLEVELS{$level} > $LOGLEVEL;
+
+ $msg = header_log($level, $msg);
+ print $LOGFH $msg;
+ $LOGFH->flush();
+};
+
+sub trace($) {
+ log_message('trace', $_[0]);
+};
+sub debug($) {
+ log_message('debug', $_[0]);
+};
+sub info($) {
+ log_message('info', $_[0]);
+};
+sub notice($) {
+ log_message('notice', $_[0]);
+};
+sub warn($) {
+ log_message('warn', $_[0]);
+};
+sub warning($) {
+ log_message('warning', $_[0]);
+};
+sub error($) {
+ log_message('error', $_[0]);
+};
+sub critical($) {
+ log_message('critical', $_[0]);
+};
+sub alert($) {
+ log_message('alert', $_[0]);
+};
+sub emergency($) {
+ log_message('emergency', $_[0]);
+};
+
+sub logdie($) {
+ my ($msg) = @_;
+ critical($msg);
+ die($msg);
+};
+sub cluck($) {
+ my ($msg) = @_;
+ my $longmess = Carp::longmess();
+ $longmess =~ s/^/ /mg;
+ $msg .= "\n".$longmess;
+ warning($msg);
+};
+sub confess($) {
+ my ($msg) = @_;
+ my $longmess = Carp::longmess();
+ $longmess =~ s/^/ /mg;
+ $msg .= "\n".$longmess;
+ error($msg);
+ die($msg);
+};
+
+1;
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Mailin.pm b/trunk/Echolot/Mailin.pm
new file mode 100644
index 0000000..a2f2abb
--- /dev/null
+++ b/trunk/Echolot/Mailin.pm
@@ -0,0 +1,252 @@
+package Echolot::Mailin;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Mailin - Incoming Mail Dispatcher for Echolot
+
+=head1 DESCRIPTION
+
+
+=cut
+
+use strict;
+use English;
+use Echolot::Globals;
+use Echolot::Log;
+use Fcntl ':flock'; # import LOCK_* constants
+use POSIX; # import SEEK_* constants (older perls don't have SEEK_ in Fcntl)
+
+
+sub make_sane_name() {
+ my $result = time().'.'.$PROCESS_ID.'_'.Echolot::Globals::get()->{'internal_counter'}++.'.'.Echolot::Globals::get()->{'hostname'};
+ return $result;
+};
+
+sub sane_move($$) {
+ my ($from, $to) = @_;
+
+ my $link_success = link($from, $to);
+ $link_success or
+ Echolot::Log::warn("Cannot link $from to $to: $!."),
+ return 0;
+ #- Trying move"),
+ #rename($from, $to) or
+ # cluck("Renaming $from to $to didn't work either: $!"),
+ # return 0;
+
+ $link_success && (unlink($from) or
+ Echolot::Log::warn("Cannot unlink $from: $!.") );
+ return 1;
+};
+
+sub handle($) {
+ my ($lines) = @_;
+
+ my $i=0;
+ my $body = '';
+ my $header = '';
+ my $to;
+ for ( ; $i < scalar @$lines; $i++) {
+ my $line = $lines->[$i];
+ chomp($line);
+ last if $line eq '';
+ $header .= $line."\n";
+
+ if ($line =~ m/^To:\s*(.*?)\s*$/) {
+ $to = $1;
+ };
+ };
+ for ( ; $i < scalar @$lines; $i++) {
+ $body .= $lines->[$i];
+ };
+
+ (defined $to) or
+ Echolot::Log::info("No To header found in mail."),
+ return 0;
+
+ my $address_result = Echolot::Tools::verify_address_tokens($to) or
+ Echolot::Log::debug("Verifying '$to' failed."),
+ return 0;
+
+ my $type = $address_result->{'token'};
+ my $timestamp = $address_result->{'timestamp'};
+
+ Echolot::Conf::remailer_conf($body, $type, $timestamp), return 1 if ($type =~ /^conf\./);
+ Echolot::Conf::remailer_key($body, $type, $timestamp), return 1 if ($type =~ /^key\./);
+ Echolot::Conf::remailer_help($body, $type, $timestamp), return 1 if ($type =~ /^help\./);
+ Echolot::Conf::remailer_stats($body, $type, $timestamp), return 1 if ($type =~ /^stats\./);
+ Echolot::Conf::remailer_adminkey($body, $type, $timestamp), return 1 if ($type =~ /^adminkey\./);
+
+ Echolot::Pinger::receive($header, $body, $type, $timestamp), return 1 if ($type eq 'ping');
+ Echolot::Chain::receive($header, $body, $type, $timestamp), return 1 if ($type eq 'chainping');
+
+ Echolot::Log::warn("Didn't know what to do with '$to'."),
+ return 0;
+};
+
+sub handle_file($) {
+ my ($file) = @_;
+
+ open (FH, $file) or
+ Echolot::Log::warn("Cannot open file $file: $!,"),
+ return 0;
+ my @lines = <FH>;
+ my $body = join('', <FH>);
+ close (FH) or
+ Echolot::Log::warn("Cannot close file $file: $!.");
+
+ return handle(\@lines);
+};
+
+sub read_mbox($) {
+ my ($file) = @_;
+
+ my @mail;
+ my $mail = [];
+ my $blank = 1;
+
+ open(FH, '+<'. $file) or
+ Echolot::Log::warn("cannot open '$file': $!."),
+ return undef;
+ flock(FH, LOCK_EX) or
+ Echolot::Log::warn("cannot gain lock on '$file': $!."),
+ return undef;
+
+ while(<FH>) {
+ if($blank && /\AFrom .*\d{4}/) {
+ push(@mail, $mail) if scalar(@{$mail});
+ $mail = [ $_ ];
+ $blank = 0;
+ } else {
+ $blank = m#\A\Z# ? 1 : 0;
+ push @$mail, $_;
+ }
+ }
+ push(@mail, $mail) if scalar(@{$mail});
+
+ seek(FH, 0, SEEK_SET) or
+ Echolot::Log::warn("cannot seek to start of '$file': $!."),
+ return undef;
+ truncate(FH, 0) or
+ Echolot::Log::warn("cannot truncate '$file' to zero size: $!."),
+ return undef;
+ flock(FH, LOCK_UN) or
+ Echolot::Log::warn("cannot release lock on '$file': $!."),
+ return undef;
+ close(FH);
+
+ return \@mail;
+}
+
+sub read_maildir($) {
+ my ($dir) = @_;
+
+ my @mail;
+
+ my @files;
+ for my $sub (qw{new cur}) {
+ opendir(DIR, $dir.'/'.$sub) or
+ Echolot::Log::warn("Cannot open direcotry '$dir/$sub': $!."),
+ return undef;
+ push @files, map { $sub.'/'.$_ } grep { ! /^\./ } readdir(DIR);
+ closedir(DIR) or
+ Echolot::Log::warn("Cannot close direcotry '$dir/$sub': $!.");
+ };
+
+ for my $file (@files) {
+ $file =~ /^(.*)$/s or
+ Echolot::Log::confess("I really should match here. ('$file').");
+ $file = $1;
+
+ my $mail = [];
+ open(FH, $dir.'/'.$file) or
+ Echolot::Log::warn("cannot open '$dir/$file': $!."),
+ return undef;
+ @$mail = <FH>;
+ close(FH);
+
+ push @mail, $mail;
+ };
+
+ for my $file (@files) {
+ unlink $dir.'/'.$file or
+ Echolot::Log::warn("cannot unlink '$dir/$file': $!.");
+ };
+
+
+ return \@mail;
+}
+
+sub storemail($$) {
+ my ($path, $mail) = @_;
+
+ my $tmpname = $path.'/tmp/'.make_sane_name();
+ open (F, '>'.$tmpname) or
+ Echolot::Log::warn("Cannot open $tmpname: $!."),
+ return undef;
+ print F join ('', @$mail);
+ close F;
+
+ my $i;
+ for ($i = 0; $i < 5; $i++ ) {
+ my $targetname = $path.'/cur/'.make_sane_name();
+ sane_move($tmpname, $targetname) or
+ sleep 1, next;
+ last;
+ };
+
+ return undef if ($i == 5);
+ return 1;
+};
+
+sub process() {
+ my $inmail = Echolot::Config::get()->{'mailin'};
+ my $mailerrordir = Echolot::Config::get()->{'mailerrordir'};
+
+ my $mails = (-d $inmail) ?
+ read_maildir($inmail) :
+ ( ( -e $inmail ) ? read_mbox($inmail) : [] );
+
+ Echolot::Globals::get()->{'storage'}->delay_commit();
+ for my $mail (@$mails) {
+ unless (handle($mail)) {
+ if (Echolot::Config::get()->{'save_errormails'}) {
+ Echolot::Log::info("Saving mail with unknown destination (probably a bounce) to mail-errordir.");
+ my $name = make_sane_name();
+ storemail($mailerrordir, $mail) or
+ Echolot::Log::warn("Could not store a mail.");
+ } else {
+ Echolot::Log::info("Trashing mail with unknown destination (probably a bounce).");
+ };
+ };
+ };
+ Echolot::Globals::get()->{'storage'}->enable_commit();
+};
+
+1;
+
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Pinger.pm b/trunk/Echolot/Pinger.pm
new file mode 100644
index 0000000..d782edd
--- /dev/null
+++ b/trunk/Echolot/Pinger.pm
@@ -0,0 +1,211 @@
+package Echolot::Pinger;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Pinger - actual sending and receiving of Pings.
+
+=head1 DESCRIPTION
+
+This package provides functions for sending out and receiving pings.
+
+=cut
+
+use strict;
+use English;
+use Echolot::Log;
+use Echolot::Pinger::Mix;
+use Echolot::Pinger::CPunk;
+
+sub do_mix_ping($$$$$$) {
+ my ($address, $type, $keyid, $to, $with_from, $body) = @_;
+
+ ($type eq 'mix') or
+ Echolot::Log::warn("types should really be mix ($type)."),
+ return 0;
+
+ my %key = Echolot::Globals::get()->{'storage'}->get_key($address, $type, $keyid);
+ Echolot::Pinger::Mix::ping(
+ $body,
+ $to,
+ $with_from,
+ [ $key{'nick'} ],
+ { $keyid => \%key } ) or
+ return 0;
+
+ return 1;
+};
+
+sub do_cpunk_ping($$$$$$) {
+ my ($address, $type, $keyid, $to, $with_from, $body) = @_;
+
+ my $keyhash = {};
+ if ($type ne 'cpunk-clear') {
+ my %key = Echolot::Globals::get()->{'storage'}->get_key($address, $type, $keyid);
+ $keyhash->{$keyid} = \%key;
+ };
+ Echolot::Pinger::CPunk::ping(
+ $body,
+ $to,
+ $with_from,
+ [ { address => $address,
+ keyid => $keyid,
+ encrypt => ($type ne 'cpunk-clear'),
+ pgp2compat => ($type eq 'cpunk-rsa') } ],
+ $keyhash ) or
+ return 0;
+
+ return 1;
+};
+
+sub do_ping($$$$) {
+ my ($type, $address, $key, $with_from) = @_;
+
+ my $now = time();
+ my $token = join(':', $address, $type, $key, $with_from, $now);
+ my $mac = Echolot::Tools::make_mac($token);
+ my $body = "remailer: $address\n".
+ "type: $type\n".
+ "key: $key\n".
+ "with_from: $with_from\n".
+ "sent: $now\n".
+ "mac: $mac\n".
+ Echolot::Tools::make_garbage();
+ $body = Echolot::Tools::crypt_symmetrically($body, 'encrypt');
+
+ my $to = Echolot::Tools::make_address('ping');
+ if ($type eq 'mix') {
+ do_mix_ping($address, $type, $key, $to, $with_from, $body);
+ } elsif ($type eq 'cpunk-rsa' || $type eq 'cpunk-dsa' || $type eq 'cpunk-clear') {
+ do_cpunk_ping($address, $type, $key, $to, $with_from, $body);
+ } else {
+ Echolot::Log::warn("Don't know how to handle ping type $type.");
+ return 0;
+ };
+
+ Echolot::Globals::get()->{'storage'}->register_pingout($address, $type, $key, $now);
+ return 1;
+};
+
+sub send_pings($;$) {
+ my ($scheduled_for, $which) = @_;
+
+ $which = '' unless defined $which;
+
+ my $call_intervall = Echolot::Config::get()->{'pinger_interval'};
+ my $send_every_n_calls = Echolot::Config::get()->{'ping_every_nth_time'};
+
+ my $timemod = int ($scheduled_for / $call_intervall);
+ my $this_call_id = $timemod % $send_every_n_calls;
+ my $session_id = int ($scheduled_for / ($call_intervall * $send_every_n_calls));
+
+ my @remailers = Echolot::Globals::get()->{'storage'}->get_addresses();
+ for my $remailer (@remailers) {
+ next unless $remailer->{'pingit'};
+ my $address = $remailer->{'address'};
+
+ next unless (
+ $which eq 'all' ||
+ $which eq $address ||
+ $which eq '');
+
+ for my $type (Echolot::Globals::get()->{'storage'}->get_types($address)) {
+ next unless Echolot::Config::get()->{'do_pings'}->{$type};
+ for my $key (Echolot::Globals::get()->{'storage'}->get_keys($address, $type)) {
+ next unless (
+ $which eq $address ||
+ $which eq 'all' ||
+ (($which eq '') && ($this_call_id eq (Echolot::Tools::makeShortNumHash($address.$type.$key.$session_id) % $send_every_n_calls))));
+
+ my $with_from = (int($timemod / $send_every_n_calls)) % 2;
+ Echolot::Log::debug("ping calling $type, $address, $key, $with_from.");
+ do_ping($type, $address, $key, $with_from);
+ }
+ };
+ };
+ return 1;
+};
+
+
+sub receive($$$$) {
+ my ($header, $msg, $token, $timestamp) = @_;
+
+ my $now = time();
+
+ my $body;
+ my $bot = 0;
+ my $top = 0;
+ # < 2.0beta34 didn't encrypt pings.
+ if ($msg =~ /^-----BEGIN PGP MESSAGE-----/m) {
+ # work around borken middleman remailers that have a problem with some
+ # sort of end of line characters and randhopping them through reliable
+ # remailers..
+ # they add an empty line between each usefull line
+ $msg =~ s/(\r?\n)\r?\n/$1/g if ($msg =~ /^-----BEGIN PGP MESSAGE-----\r?\n\r?\n/m);
+
+ $top = ($msg =~ m/^\S.*-----BEGIN PGP MESSAGE-----/ms) ? 1 : 0;
+ $bot = ($msg =~ m/^-----END PGP MESSAGE-----.*\S/ms) ? 1 : 0;
+
+ $body = Echolot::Tools::crypt_symmetrically($msg, 'decrypt');
+ };
+ $body = $msg unless defined $body;
+
+ my ($addr) = $body =~ /^remailer: (.*)$/m;
+ my ($type) = $body =~ /^type: (.*)$/m;
+ my ($key) = $body =~ /^key: (.*)$/m;
+ my ($sent) = $body =~ /^sent: (.*)$/m;
+ my ($with_from) = $body =~ /^with_from: (.*)$/m;
+ my ($mac) = $body =~ /^mac: (.*)$/m;
+
+ my @values = ($addr, $type, $key, defined $with_from ? $with_from : 'undef', $sent, $mac); # undef was added after 2.0.10
+ my $cleanstring = join ":", map { defined() ? $_ : "undef" } @values;
+ my @values_obsolete = ($addr, $type, $key, $sent, $mac); # <= 2.0.10
+
+ (grep { ! defined() } @values_obsolete) and
+ Echolot::Log::warn("Received ping at $timestamp has undefined values: $cleanstring."),
+ return 0;
+
+ pop @values;
+ pop @values_obsolete;
+ Echolot::Tools::verify_mac(join(':', @values), $mac) or
+ Echolot::Tools::verify_mac(join(':', @values_obsolete), $mac) or # old style without with_from
+ Echolot::Log::warn("Received ping at $timestamp has wrong mac; $cleanstring."),
+ return 0;
+
+ Echolot::Globals::get()->{'storage'}->register_pingdone($addr, $type, $key, $sent, $now - $sent) or
+ return 0;
+
+ if (defined $with_from) { # <= 2.0.10 didn't have with_from
+ my ($from) = $header =~ /From: (.*)/i;
+ $from = 'undefined' unless defined $from;
+ Echolot::Globals::get()->{'storage'}->register_fromline($addr, $type, $with_from, $from, $top, $bot);
+ };
+
+ return 1;
+};
+
+1;
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Pinger/CPunk.pm b/trunk/Echolot/Pinger/CPunk.pm
new file mode 100644
index 0000000..6cac797
--- /dev/null
+++ b/trunk/Echolot/Pinger/CPunk.pm
@@ -0,0 +1,205 @@
+package Echolot::Pinger::CPunk;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Pinger::CPunk - send cypherpunk pings
+
+=head1 DESCRIPTION
+
+This package provides functions for sending cypherpunk (type I) pings.
+
+=cut
+
+use strict;
+use English;
+use GnuPG::Interface;
+use Echolot::Log;
+
+sub encrypt_to($$$$) {
+ my ($msg, $recipient, $keys, $pgp2compat) = @_;
+
+ (defined $keys->{$recipient}) or
+ Echolot::Log::warn("Key for recipient $recipient is not defined."),
+ return undef;
+ (defined $keys->{$recipient}->{'key'}) or
+ Echolot::Log::warn("Key->key for recipient $recipient is not defined."),
+ return undef;
+ my $keyring = Echolot::Config::get()->{'tmpdir'}.'/'.
+ Echolot::Globals::get()->{'hostname'}.".".time.'.'.$PROCESS_ID.'_'.Echolot::Globals::get()->{'internalcounter'}++.'.keyring';
+
+ my $GnuPG = new GnuPG::Interface;
+ $GnuPG->call( Echolot::Config::get()->{'gnupg'} ) if (Echolot::Config::get()->{'gnupg'});
+ $GnuPG->options->hash_init(
+ homedir => Echolot::Config::get()->{'gnupghome'} );
+ $GnuPG->options->meta_interactive( 0 );
+
+ my ( $stdin_fh, $stdout_fh, $stderr_fh, $status_fh, $handles ) = Echolot::Tools::make_gpg_fds();
+ my $pid = $GnuPG->wrap_call(
+ commands => [ '--import' ],
+ command_args => [qw{--no-options --no-secmem-warning --no-default-keyring --fast-list-mode --keyring}, $keyring, '--', '-' ],
+ handles => $handles );
+ my ($stdout, $stderr, $status) = Echolot::Tools::readwrite_gpg($keys->{$recipient}->{'key'}, $stdin_fh, $stdout_fh, $stderr_fh, $status_fh);
+ waitpid $pid, 0;
+
+ ($stdout eq '') or
+ Echolot::Log::info("GnuPG returned something in stdout '$stdout' while adding key for '$recipient': So what?");
+ #($stderr eq '') or
+ #Echolot::Log::warn("GnuPG returned something in stderr: '$stderr' while adding key for '$recipient'; returning."),
+ #return undef;
+ ($status =~ /^^\[GNUPG:\] IMPORTED $recipient /m) or
+ Echolot::Log::info("GnuPG status '$status' didn't indicate key for '$recipient' was imported correctly."),
+ return undef;
+
+
+
+
+
+
+ #$msg =~ s/\r?\n/\r\n/g;
+
+
+
+
+ $GnuPG->options->hash_init(
+ armor => 1 );
+
+ ( $stdin_fh, $stdout_fh, $stderr_fh, $status_fh, $handles ) = Echolot::Tools::make_gpg_fds();
+ my $command_args = [qw{--no-options --no-secmem-warning --always-trust --no-default-keyring --textmode --cipher-algo 3DES --keyring}, $keyring, '--recipient', $recipient];
+ my $plaintextfile;
+
+ #if ($pgp2compat) {
+ # push @$command_args, qw{--pgp2};
+ #};
+ # Files are required for compaitibility with PGP 2.*
+ # we also use files in all other cases since there is a bug in either GnuPG or GnuPG::Interface
+ # that let Echolot die if in certain cases:
+ # If a key is unuseable because it expired and we want to encrypt something to it
+ # pingd dies if there is only enough time between calling encrypt() and printing the message
+ # to GnuPG. (a sleep 1 triggered that reproduceably)
+ $plaintextfile = Echolot::Config::get()->{'tmpdir'}.'/'.
+ Echolot::Globals::get()->{'hostname'}.".".time.'.'.$PROCESS_ID.'_'.Echolot::Globals::get()->{'internalcounter'}++.'.plaintext';
+ open (F, '>'.$plaintextfile) or
+ Echolot::Log::warn("Cannot open $plaintextfile for writing: $!."),
+ return 0;
+ print (F $msg);
+ close (F) or
+ Echolot::Log::warn("Cannot close $plaintextfile."),
+ return 0;
+ push @$command_args, $plaintextfile;
+
+ $pid = $GnuPG->encrypt(
+ command_args => $command_args,
+ handles => $handles );
+ ($stdout, $stderr, $status) = Echolot::Tools::readwrite_gpg('', $stdin_fh, $stdout_fh, $stderr_fh, $status_fh);
+ waitpid $pid, 0;
+
+ #($stderr eq '') or
+ #Echolot::Log::warn("GnuPG returned something in stderr: '$stderr' while encrypting to '$recipient'."),
+ #return undef;
+ ($status =~ /^\[GNUPG:\] KEYEXPIRED (\d+)/m) and
+ Echolot::Log::info("Key $recipient expired at ".scalar gmtime($1)." UTC"),
+ return undef;
+ (($status =~ /^\[GNUPG:\] BEGIN_ENCRYPTION\s/m) &&
+ ($status =~ /^\[GNUPG:\] END_ENCRYPTION\s/m)) or
+ Echolot::Log::info("GnuPG status '$status' didn't indicate message to '$recipient' was encrypted correctly (stderr: $stderr; args: ".join(' ', @$command_args).")."),
+ return undef;
+
+ unlink ($keyring) or
+ Echolot::Log::warn("Cannot unlink tmp keyring '$keyring'."),
+ return undef;
+ unlink ($keyring.'~'); # gnupg does those evil backups
+
+ (defined $plaintextfile) and
+ (unlink ($plaintextfile) or
+ Echolot::Log::warn("Cannot unlink tmp plaintextfile '$plaintextfile'."),
+ return undef);
+
+
+ my $result;
+
+ $plaintextfile .= '.asc';
+ open (F, '<'.$plaintextfile) or
+ Echolot::Log::warn("Cannot open $plaintextfile for reading: $!."),
+ return 0;
+ $result = join '', <F>;
+ close (F) or
+ Echolot::Log::warn("Cannot close $plaintextfile."),
+ return 0;
+
+ (defined $plaintextfile) and
+ (unlink ($plaintextfile) or
+ Echolot::Log::warn("Cannot unlink tmp plaintextfile '$plaintextfile'."),
+ return undef);
+
+ $result =~ s,^Version: .*$,Version: N/A,m;
+ #$result =~ s/\r?\n/\r\n/g;
+ return $result;
+};
+
+sub ping($$$$$) {
+ my ($body, $to, $with_from, $chain, $keys) = @_;
+
+ my $msg = $body;
+
+ for my $hop (reverse @$chain) {
+ my $header = '';
+ if ($with_from) {
+ my $address = Echolot::Config::get()->{'my_localpart'} . '@' .
+ Echolot::Config::get()->{'my_domain'};
+ $header = "##\nFrom: Echolot Pinger <$address>\n\n";
+ $with_from = 0;
+ };
+ # "Latent-Time: +0\n".
+ $msg = "::\n".
+ "Anon-To: $to\n".
+ "\n".
+ $header.
+ $msg;
+
+ if ($hop->{'encrypt'}) {
+ my $encrypted = encrypt_to($msg, $hop->{'keyid'}, $keys, $hop->{'pgp2compat'});
+ (defined $encrypted) or
+ Echolot::Log::debug("Encrypted is undefined."),
+ return undef;
+ $msg = "::\n".
+ "Encrypted: PGP\n".
+ "\n".
+ $encrypted;
+ };
+ $to = $hop->{'address'};
+ }
+
+ Echolot::Tools::send_message(
+ To => $to,
+ Body => $msg
+ );
+
+ return 1;
+};
+
+1;
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Pinger/Mix.pm b/trunk/Echolot/Pinger/Mix.pm
new file mode 100644
index 0000000..66eaf25
--- /dev/null
+++ b/trunk/Echolot/Pinger/Mix.pm
@@ -0,0 +1,139 @@
+package Echolot::Pinger::Mix;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Pinger::Mix - send mix pings
+
+=head1 DESCRIPTION
+
+This package provides functions for sending mixmaster (type II) pings.
+
+=cut
+
+use strict;
+use English;
+use IO::Handle;
+use Echolot::Log;
+
+sub ping($$$$$) {
+ my ($body, $to, $with_from, $chain, $keys) = @_;
+
+ my $chaincomma = join (',', @$chain);
+
+ my $keyring = Echolot::Config::get()->{'mixhome'}.'/pubring.mix';
+ open (F, '>'.$keyring) or
+ Echolot::Log::warn("Cannot open $keyring for writing: $!."),
+ return 0;
+ for my $keyid (keys %$keys) {
+ print (F $keys->{$keyid}->{'summary'}, "\n\n");
+ print (F $keys->{$keyid}->{'key'},"\n\n");
+ };
+ close (F) or
+ Echolot::Log::warn("Cannot close $keyring: $!."),
+ return 0;
+
+ my $type2list = Echolot::Config::get()->{'mixhome'}.'/type2.list';
+ open (F, '>'.$type2list) or
+ Echolot::Log::warn("Cannot open $type2list for writing: $!."),
+ return 0;
+ for my $keyid (keys %$keys) {
+ print (F $keys->{$keyid}->{'summary'}, "\n");
+ };
+ close (F) or
+ Echolot::Log::warn("Cannot close $type2list: $!."),
+ return 0;
+
+ my $mixcfg = Echolot::Config::get()->{'mixhome'}.'/mix.cfg';
+ my $address = Echolot::Config::get()->{'my_localpart'} . '@' .
+ Echolot::Config::get()->{'my_domain'};
+ my $sendmail = Echolot::Config::get()->{'sendmail'};
+ open (F, ">$mixcfg") or
+ Echolot::Log::warn("Cannot open $mixcfg for writing: $!."),
+ return 0;
+ print (F "REMAIL n\n");
+ print (F "NAME Echolot Pinger\n");
+ print (F "ADDRESS $address\n");
+ print (F "PUBRING pubring.mix\n");
+ print (F "TYPE2LIST type2.list\n");
+ print (F "SENDMAIL $sendmail -f $address -t\n");
+ print (F "VERBOSE 0\n");
+ print (F "INDUMMYP 0\n");
+ print (F "OUTDUMMYP 0\n");
+ close (F) or
+ Echolot::Log::warn("Cannot close $mixcfg: $!."),
+ return 0;
+
+ my ($stdinR, $stdinW) = (IO::Handle->new(), IO::Handle->new());
+ my ($stdoutR, $stdoutW) = (IO::Handle->new(), IO::Handle->new());
+ my ($stderrR, $stderrW) = (IO::Handle->new(), IO::Handle->new());
+ pipe $stdinR, $stdinW;
+ pipe $stdoutR, $stdoutW;
+ pipe $stderrR, $stderrW;
+ my $pid = fork();
+ defined $pid or
+ Echolot::Log::warn("Cannot fork for calling mixmaster: $!."),
+ return 0;
+ unless ($pid) { # child
+ $stdinW->close;
+ $stdoutR->close;
+ $stderrR->close;
+ close STDIN;
+ close STDOUT;
+ close STDERR;
+ open (STDIN, "<&".$stdinR->fileno) or Echolot::Log::warn ("Cannot dup stdinR (fd ".$stdinR->fileno.") as STDIN: $!");
+ open (STDOUT, ">&".$stdoutW->fileno) or Echolot::Log::warn ("Cannot dup stdoutW (fd ".$stdoutW->fileno.") as STDOUT: $!");
+ open (STDERR, ">&".$stderrW->fileno) or Echolot::Log::warn ("Cannot dup stderrW (fd ".$stderrW->fileno.") as STDERE: $!");
+ $ENV{'MIXPATH'} = Echolot::Config::get()->{'mixhome'};
+ { exec(Echolot::Config::get()->{'mixmaster'}, qw{-m -S -l}, $chaincomma); };
+ Echolot::Log::warn("Cannot exec mixpinger: $!.");
+ exit(1);
+ };
+ $stdinR->close;
+ $stdoutW->close;
+ $stderrW->close;
+
+ my $msg;
+ $msg .= "From: Echolot Pinger <$address>\n" if $with_from;
+ $msg .= "To: $to\n\n$body\n";
+
+ Echolot::Log::debug("mixping: fds: stdinW $stdinW; stdoutR $stdoutR; stderrR $stderrR."),
+ my ($stdout, $stderr, undef) = Echolot::Tools::readwrite_gpg($msg, $stdinW, $stdoutR, $stderrR, undef);
+ waitpid $pid, 0;
+
+ $stderr =~ s/\n+$//;
+ Echolot::Log::debug("Mixmaster said on unfiltered stderr: $stderr") if ($stderr ne '');
+ $stderr =~ s/^Chain: .*//mg;
+ $stderr =~ s/^Warning: The message has a From: line.*//mg;
+ $stderr =~ s/\n+$//;
+ Echolot::Log::info("Mixmaster said on stdout: $stdout") if ($stdout ne '');
+ Echolot::Log::warn("Mixmaster said on stderr: $stderr") if ($stderr ne '');
+
+ return 1;
+};
+
+1;
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Report.pm b/trunk/Echolot/Report.pm
new file mode 100644
index 0000000..1c2a4ae
--- /dev/null
+++ b/trunk/Echolot/Report.pm
@@ -0,0 +1,70 @@
+package Echolot::Report;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Report - Summarize status of remailers
+
+=head1 DESCRIPTION
+
+This package prints the summary of remailers/addresses.
+
+=cut
+
+use strict;
+use English;
+use Echolot::Log;
+
+sub print_summary(;$) {
+ my ($manual) = @_;
+
+ my @addresses = sort { $a->{'address'} cmp $b->{'address'} } Echolot::Globals::get()->{'storage'}->get_addresses();
+ my $report = "*** Status summary ***\n";
+
+ for my $remailer (@addresses) {
+ my $addr = $remailer->{'address'};
+ $report .= "$addr (ID: $remailer->{'id'}): ".uc($remailer->{'status'})."; ".
+ "Fetch/Ping/Show: ".
+ ($remailer->{'fetch'} ? '1' : '0') .
+ ($remailer->{'pingit'} ? '1' : '0') .
+ ($remailer->{'showit'} ? '1' : '0') .
+ "; TTL: $remailer->{'ttl'}\n";
+ $report .= " Resurection TTL: $remailer->{'resurrection_ttl'}\n" if (defined $remailer->{'resurrection_ttl'} && ($remailer->{'status'} eq 'ttl timeout'));
+ for my $type (Echolot::Globals::get()->{'storage'}->get_types($addr)) {
+ $report .= " Type: $type: ".join(', ', Echolot::Globals::get()->{'storage'}->get_keys($addr, $type))."\n";
+ };
+ };
+ if (defined $manual) {
+ Echolot::Log::notice($report);
+ } else {
+ Echolot::Log::info($report);
+ }
+
+ return 1;
+};
+
+1;
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Scheduler.pm b/trunk/Echolot/Scheduler.pm
new file mode 100644
index 0000000..32d859f
--- /dev/null
+++ b/trunk/Echolot/Scheduler.pm
@@ -0,0 +1,196 @@
+package Echolot::Scheduler;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Scheduler - Task selector/scheduler for echolot
+
+=head1 DESCRIPTION
+
+This package provides several functions for scheduling tasks within
+the ping daemon.
+
+=over
+
+=cut
+
+use strict;
+use English;
+use Echolot::Log;
+
+my $ORDER = 1;
+
+=item B<new> ()
+
+Creates a new scheduler object.
+
+=cut
+sub new {
+ my ($class, %params) = @_;
+ my $self = {};
+ bless $self, $class;
+ return $self;
+};
+
+=item B<add> (I<name>, I<interval>, I<offset>, I<missok>, I<what>)
+
+Adds a task with I<name> to the list of tasks. Every I<interval> seconds
+I<what> is called. If for example I<interval> is 3600 - meaning I<what>
+should be executed hourly - setting I<offset> to 600 would mean that
+it get's called 10 minutes after the hour.
+
+I<missok> indicates that it is ok to miss one run of this job. This can happen
+if we run behind schedule for instance.
+
+=cut
+sub add($$$$$$) {
+ my ($self, $name, $interval, $offset, $missok, $what) = @_;
+
+ Echolot::Log::logdie("Must not add zero intervall for job $name.")
+ unless $interval;
+
+ if (defined $self->{'tasks'}->{$name}) {
+ @{ $self->{'schedule'} } = grep { $_->{'name'} ne $name } @{ $self->{'schedule'} };
+ };
+
+ $self->{'tasks'}->{$name} =
+ {
+ interval => $interval,
+ offset => $offset,
+ what => $what,
+ order => $ORDER++,
+ missok => $missok,
+ };
+
+ $self->schedule($name, 1);
+
+ return 1;
+};
+
+=item B<schedule> (I<name>, I<reschedule>, [ I<for>, [I<arguments>]] )
+
+Schedule execution of I<name> for I<for>. If I<for> is not given it is calculated
+from I<interval> and I<offset> passed to B<new>. if I<reschedule> is set
+the task will be rescheduled when it's done (according to its interval).
+You may also give arguments to passed to the task.
+
+=cut
+sub schedule($$$;$$) {
+ my ($self, $name, $reschedule, $for, $arguments) = @_;
+
+ (defined $self->{'tasks'}->{$name}) or
+ Echolot::Log::warn("Task $name is not defined."),
+ return 0;
+
+ my $interval = $self->{'tasks'}->{$name}->{'interval'};
+ my $offset = $self->{'tasks'}->{$name}->{'offset'};
+
+
+ unless (defined $for) {
+ ($interval < 0) and
+ return 1;
+ my $now = time();
+ $for = $now - $now % $interval + $offset;
+ ($for <= $now) and $for += $interval;
+ my $cnt = 0;
+ while ($self->{'tasks'}->{$name}->{'missok'} && ($for <= $now)) {
+ $for += $interval;
+ $cnt ++;
+ };
+ Echolot::Log::debug("Skipping $cnt runs of $name.") if $cnt;
+ };
+
+ $arguments = [] unless defined $arguments;
+
+ push @{ $self->{'schedule'} },
+ {
+ start => $for,
+ order => $self->{'tasks'}->{$name}->{'order'},
+ name => $name,
+ arguments => $arguments,
+ reschedule => $reschedule
+ };
+
+ @{ $self->{'schedule'} } = sort { $a->{'start'} <=> $b->{'start'} or $a->{'order'} <=> $b->{'order'} }
+ @{ $self->{'schedule'} };
+
+ return 1;
+};
+
+=item B<run> ()
+
+Start the scheduling run.
+
+It will run forever or until a task with I<what> == 'exit' is executed.
+
+=cut
+sub run($) {
+ my ($self) = @_;
+
+ (defined $self->{'schedule'}->[0]) or
+ Echolot::Log::warn("Scheduler is empty."),
+ return 0;
+
+ while(1) {
+ my $now = time();
+ my $task = $self->{'schedule'}->[0];
+ if ($task->{'start'} < $now) {
+ Echolot::Log::warn("Task $task->{'name'} could not be started on time.")
+ unless ($task->{'start'} == 0);
+ } else {
+ Echolot::Log::debug("zZzZZzz.");
+ $PROGRAM_NAME = "pingd [sleeping]";
+ sleep ($task->{'start'} - $now);
+ };
+
+ (time() < $task->{'start'}) and
+ next;
+
+ $now = $task->{'start'};
+ do {
+ $task = shift @{ $self->{'schedule'} };
+ my $name = $task->{'name'};
+ $PROGRAM_NAME = "pingd [executing $name]";
+ (defined $self->{'tasks'}->{$name}) or
+ Echolot::Log::cluck("Task $task->{'name'} is not defined.");
+
+ my $what = $self->{'tasks'}->{$name}->{'what'};
+ Echolot::Log::debug("Running $name (was scheduled for ".(time()-$now)." seconds ago).");
+ last if ($what eq 'exit');
+ &$what( $now, @{ $task->{'arguments'} } );
+ $self->schedule($name, 1, $now + $self->{'tasks'}->{$name}->{'interval'}) if
+ ($task->{'reschedule'} && $self->{'tasks'}->{$name}->{'interval'} > 0);
+
+ (defined $self->{'schedule'}->[0]) or
+ Echolot::Log::warn("Scheduler is empty."),
+ return 0;
+ } while ($now >= $self->{'schedule'}->[0]->{'start'});
+ };
+
+ return 1;
+};
+
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Stats.pm b/trunk/Echolot/Stats.pm
new file mode 100644
index 0000000..852b11b
--- /dev/null
+++ b/trunk/Echolot/Stats.pm
@@ -0,0 +1,983 @@
+package Echolot::Stats;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Stats - produce Stats, keyrings et al
+
+=head1 DESCRIPTION
+
+This package provides functions for generating remailer stats,
+and keyrings.
+
+=cut
+
+use strict;
+use English;
+use Echolot::Log;
+
+my $STATS_DAYS;
+my $SECONDS_PER_DAY;
+my $WEIGHT;
+
+my %LAST_BROKENCHAIN_RUN;
+my %BROKEN_CHAINS;
+
+sub make_date() {
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime();
+ sprintf("%s %02d %s %4d %02d:%02d:%02d GMT",
+ Echolot::Tools::make_dayname($wday),
+ $mday,
+ Echolot::Tools::make_monthname($mon),
+ $year + 1900,
+ $hour,
+ $min,
+ $sec);
+};
+
+sub make_min_hr($$) {
+ my ($sec, $includesec) = @_;
+ my ($s, $m, $h);
+
+ if (defined $sec) {
+ $s = $sec % 60;
+ $m = $sec / 60 % 60;
+ $h = int ($sec / 60 / 60);
+ };
+ if ((! defined $sec) || ($sec < 0) || ($h > 99)) {
+ $h = 99;
+ $m = 59;
+ $s = 59;
+ };
+
+ if ($includesec) {
+ if ($h) { return sprintf ("%2d:%02d:%02d", $h, $m, $s); }
+ elsif ($m) { return sprintf ( " %2d:%02d", $m, $s); }
+ else { return sprintf ( " %2d", $s); };
+ } else {
+ if ($h) { return sprintf ("%2d:%02d", $h, $m); }
+ else { return sprintf ( " :%02d", $m); };
+ };
+};
+
+sub build_list1_latencystr($) {
+ my ($lat) = @_;
+
+ my $str = '?' x $STATS_DAYS;
+ for my $day (0 .. $STATS_DAYS - 1) {
+ substr($str, $STATS_DAYS - 1 - $day, 1) =
+ (defined $lat->[$day]) ?
+ ($lat->[$day] < 300 ? '#' :
+ ($lat->[$day] < 3600 ? '*' :
+ ($lat->[$day] < 14400 ? '+' :
+ ($lat->[$day] < 86400 ? '-' :
+ ($lat->[$day] < 172800 ? '.' :
+ '_'
+ )))))
+ : ' ';
+ };
+ return $str;
+}
+
+sub build_list2_latencystr($) {
+ my ($lat) = @_;
+
+ my $str = '?' x $STATS_DAYS;
+ for my $day (0 .. $STATS_DAYS - 1) {
+ substr($str, $STATS_DAYS - 1 - $day, 1) =
+ (defined $lat->[$day]) ?
+ ($lat->[$day] < 20*60 ? '0' :
+ ($lat->[$day] < 1*3600 ? '1' :
+ ($lat->[$day] < 2*3600 ? '2' :
+ ($lat->[$day] < 3*3600 ? '3' :
+ ($lat->[$day] < 4*3600 ? '4' :
+ ($lat->[$day] < 5*3600 ? '5' :
+ ($lat->[$day] < 6*3600 ? '6' :
+ ($lat->[$day] < 7*3600 ? '7' :
+ ($lat->[$day] < 8*3600 ? '8' :
+ ($lat->[$day] < 9*3600 ? '9' :
+ ($lat->[$day] < 12*3600 ? 'A' :
+ ($lat->[$day] < 18*3600 ? 'B' :
+ ($lat->[$day] < 24*3600 ? 'C' :
+ ($lat->[$day] < 30*3600 ? 'D' :
+ ($lat->[$day] < 36*3600 ? 'E' :
+ ($lat->[$day] < 42*3600 ? 'F' :
+ ($lat->[$day] < 48*3600 ? 'G' :
+ 'H'
+ )))))))))))))))))
+ : '?';
+ };
+ return $str;
+}
+
+sub build_list2_reliabilitystr($) {
+ my ($rel) = @_;
+
+ my $str = '?' x $STATS_DAYS;
+ for my $day (0 .. $STATS_DAYS - 1) {
+ substr($str, $STATS_DAYS - 1 - $day, 1) =
+ (defined $rel->[$day]) ?
+ (($rel->[$day] >= 0.9999) ?
+ #(($rel->[$day] == 1) ?
+ '+' :
+ (int ($rel->[$day]*10)))
+ : '?';
+ };
+ return $str;
+}
+
+sub build_list2_capsstr($) {
+ my ($caps) = @_;
+
+ my %caps;
+ $caps{'middle'} = ($caps =~ m/\bmiddle\b/i);
+ $caps{'post'} = ($caps =~ m/\bpost\b/i) || ($caps =~ m/\banon-post-to\b/i);
+ $caps{'mix'} = ($caps =~ m/\bmix\b/i);
+ $caps{'remix'} = ($caps =~ m/\bremix\b/i);
+ $caps{'remix2'} = ($caps =~ m/\bremix2\b/i);
+ $caps{'hybrid'} = ($caps =~ m/\bhybrid\b/i);
+ $caps{'repgp2'} = ($caps =~ m/\brepgp2\b/i);
+ $caps{'repgp'} = ($caps =~ m/\brepgp\b/i);
+ $caps{'pgponly'} = ($caps =~ m/\bpgponly\b/i);
+ $caps{'ext'} = ($caps =~ m/\bext\b/i);
+ $caps{'max'} = ($caps =~ m/\bmax\b/i);
+ $caps{'test'} = ($caps =~ m/\btest\b/i);
+ $caps{'latent'} = ($caps =~ m/\blatent\b/i);
+ $caps{'ek'} = ($caps =~ m/\bek\b/i);
+ $caps{'ekx'} = ($caps =~ m/\bekx\b/i);
+ $caps{'esub'} = ($caps =~ m/\besub\b/i);
+ $caps{'inflt'} = ($caps =~ m/\binflt\d+\b/i);
+ $caps{'rhop'} = ($caps =~ m/\brhop\d+\b/i);
+ ($caps{'klen'}) = ($caps =~ m/\bklen(\d+)\b/i);
+
+ my $str =
+ ($caps{'middle'} ? 'D' : ' ') .
+ ($caps{'post'} ? 'P' : ' ') .
+ ($caps{'remix2'} ? '2' : ($caps{'remix'} ? 'R' : ($caps{'mix'} ? 'M' : ' ' ))) .
+ ($caps{'hybrid'} ? 'H' : ' ') .
+ ($caps{'repgp2'} ? '2' : ($caps{'repgp'} ? 'G' : ' ' )) .
+ ($caps{'pgponly'} ? 'O' : ' ') .
+ ($caps{'ext'} ? 'X' : ' ') .
+ ($caps{'max'} ? 'A' : ' ') .
+ ($caps{'test'} ? 'T' : ' ') .
+ ($caps{'latent'} ? 'L' : ' ') .
+ ($caps{'ekx'} ? 'E' : ($caps{'ek'} ? 'e' : ' ' )) .
+ ($caps{'esub'} ? 'U' : ' ') .
+ ($caps{'inflt'} ? 'I' : ' ') .
+ ($caps{'rhop'} ? 'N' : ' ') .
+ (defined $caps{'klen'} ?
+ ($caps{'klen'} >= 900 ? '9' : (
+ $caps{'klen'} >= 800 ? '8' : (
+ $caps{'klen'} >= 700 ? '7' : (
+ $caps{'klen'} >= 600 ? '6' : (
+ $caps{'klen'} >= 500 ? '5' : (
+ $caps{'klen'} >= 400 ? '4' : (
+ $caps{'klen'} >= 300 ? '3' : (
+ $caps{'klen'} >= 200 ? '2' : (
+ $caps{'klen'} >= 100 ? '1' : '0'
+ )))))))))
+ : ' ');
+ return $str;
+}
+
+sub median($) {
+ my ($arr) = @_;
+
+ my $cnt = scalar @$arr;
+ if ($cnt == 0) {
+ return undef;
+ } elsif ($cnt % 2 == 0) {
+ return (($arr->[ int(($cnt - 1 ) / 2) ] + $arr->[ int($cnt / 2) ] ) / 2);
+ } else {
+ return $arr->[ int(($cnt - 1 ) / 2) ];
+ };
+};
+
+# how many % (0-1) values of @$lats are greater than $lat.
+# $@lats needs to be sorted
+sub percentile($$) {
+ my ($lat, $lats) = @_;
+
+ my $num = scalar @$lats;
+ my $i;
+ for ($i=0; $i < $num; $i++) {
+ last if $lat < $lats->[$i];
+ }
+ return ($num - $i) / $num;
+}
+
+sub calculate($$) {
+ my ($addr, $types) = @_;
+ my $now = time();
+
+ my $SKEW_ABS = 15*60;
+ my $SKEW_PERCENT = 0.80;
+
+ my @out;
+ my @done;
+
+ for my $type (@$types) {
+ next unless Echolot::Globals::get()->{'storage'}->has_type($addr, $type);
+ my @keys = Echolot::Globals::get()->{'storage'}->get_keys($addr, $type);
+ for my $key (@keys) {
+ push @out, grep {$_ > $now - $STATS_DAYS * $SECONDS_PER_DAY} Echolot::Globals::get()->{'storage'}->get_pings($addr, $type, $key, 'out');
+ push @done, grep {$_->[0] > $now - $STATS_DAYS * $SECONDS_PER_DAY} Echolot::Globals::get()->{'storage'}->get_pings($addr, $type, $key, 'done');
+ };
+ };
+
+ my @latency_total = map { $_->[1] } @done;
+ my @latency_day;
+ my $sent_total;
+ my $received_total = 0;
+ my @sent_day;
+ my @received_day;
+ for my $done (@done) {
+ push @{ $latency_day [int(($now - $done->[0]) / $SECONDS_PER_DAY)] }, $done->[1];
+ my $day = int(($now - $done->[0]) / $SECONDS_PER_DAY);
+ my $weight = $WEIGHT->[$day];
+ $sent_total += $weight; $sent_day [$day] ++;
+ $received_total += $weight; $received_day[$day] ++;
+ };
+
+ @latency_total = sort { $a <=> $b } @latency_total;
+ my $latency_median = median (\@latency_total);
+ my @latency_median_day;
+ for ( 0 .. $STATS_DAYS - 1 ) {
+ @{$latency_day[$_]} = defined $latency_day[$_] ? (sort { $a <=> $b } @{$latency_day[$_]}) : ();
+ $latency_median_day[$_] = median ( $latency_day[$_] );
+ }
+
+ if (scalar @out) {
+ my @p = ( scalar @latency_total ) ?
+ map { #printf(STDERR "($now - $_ - $SKEW_ABS)/$SKEW_PERCENT ".
+ #"%s in (%s): %s\n", ($now - $_ - $SKEW_ABS)/$SKEW_PERCENT, join(',', @latency_total),
+ #percentile( ($now - $_ - $SKEW_ABS)/$SKEW_PERCENT , \@latency_total ));
+ percentile( ($now - $_ - $SKEW_ABS)/$SKEW_PERCENT , \@latency_total ) } @out :
+ map { 0 } @out;
+ for (my $i=0; $i < scalar @out; $i++) {
+ my $day = int(($now - $out[$i]) / $SECONDS_PER_DAY);
+ my $weight = $WEIGHT->[$day];
+ $sent_total += $weight; $sent_day [$day] ++;
+ $received_total += $weight * $p[$i]; $received_day[$day] += $p[$i];
+ };
+ };
+ #printf STDERR "$received_total / %s\n", (defined $sent_total ? $sent_total : 'n/a');
+ $received_total /= $sent_total if ($sent_total);
+ for ( 0 .. $STATS_DAYS - 1 ) {
+ $received_day[$_] /= $sent_day[$_] if ($sent_day[$_]);
+ };
+
+
+
+ return {
+ avr_latency => $latency_median,
+ avr_reliability => $received_total,
+ latency_day => \@latency_median_day,
+ reliability_day => \@received_day
+ };
+};
+
+sub write_file($$$$) {
+ my ($filebasename, $html_template, $expires, $output) = @_;
+
+ my $filename = $filebasename.'.txt';
+ open(F, '>'.$filename) or
+ Echolot::Log::warn("Cannot open $filename: $!."),
+ return 0;
+ print F $output;
+ close (F);
+ if (defined $expires) {
+ Echolot::Tools::write_meta_information($filename,
+ Expires => time + $expires) or
+ Echolot::Log::debug ("Error while writing meta information for $filename."),
+ return 0;
+ };
+ return 1 unless defined $html_template;
+
+ if (defined $output) {
+ $output =~ s/&/&amp;/g;
+ $output =~ s/"/&quot;/g;
+ $output =~ s/</&lt;/g;
+ $output =~ s/>/&gt;/g;
+ };
+ Echolot::Tools::write_HTML_file($filebasename, $html_template, $expires, list => $output);
+
+ return 1;
+};
+
+sub build_mlist1($$$$$;$) {
+ my ($rems, $broken1, $broken2, $sameop, $filebasename, $html_template) = @_;
+
+ my $output = '';
+ $output .= sprintf "\nGroups of remailers sharing a machine or operator:\n$sameop\n" if (defined $sameop);
+ $output .= sprintf "\nBroken type-I remailer chains:\n$broken1\n" if (defined $broken1);
+ $output .= sprintf "\nBroken type-II remailer chains:\n$broken2\n" if (defined $broken2);
+
+ $output .= sprintf "Last update: %s\n", make_date();
+ $output .= sprintf "mixmaster history latency uptime\n";
+ $output .= sprintf "--------------------------------------------\n";
+
+ for my $remailer (@$rems) {
+ $output .= sprintf "%-14s %-12s %8s %6.2f%%\n",
+ substr($remailer->{'nick'},0,14),
+ build_list1_latencystr($remailer->{'stats'}->{'latency_day'}),
+ make_min_hr($remailer->{'stats'}->{'avr_latency'}, 1),
+ $remailer->{'stats'}->{'avr_reliability'} * 100;
+ };
+
+ write_file($filebasename, $html_template, Echolot::Config::get()->{'buildstats'}, $output) or
+ Echolot::Log::debug("writefile failed."),
+ return 0;
+ return 1;
+};
+
+sub build_rlist1($$$$$;$) {
+ my ($rems, $broken1, $broken2, $sameop, $filebasename, $html_template) = @_;
+
+ my $output = '';
+ for my $remailer (sort {$a->{'caps'} cmp $b->{'caps'}} @$rems) {
+ $output .= $remailer->{'caps'}."\n"
+ }
+
+ $output .= sprintf "\nGroups of remailers sharing a machine or operator:\n$sameop\n" if (defined $sameop);
+ $output .= sprintf "\nBroken type-I remailer chains:\n$broken1\n" if (defined $broken1);
+ $output .= sprintf "\nBroken type-II remailer chains:\n$broken2\n" if (defined $broken2);
+
+ $output .= sprintf "\n";
+ $output .= sprintf "Last update: %s\n", make_date();
+ $output .= sprintf "remailer email address history latency uptime\n";
+ $output .= sprintf "-----------------------------------------------------------------------\n";
+
+ for my $remailer (@$rems) {
+ $output .= sprintf "%-8s %-32s %-12s %8s %6.2f%%\n",
+ substr($remailer->{'nick'},0,8),
+ substr($remailer->{'address'},0,32),
+ build_list1_latencystr($remailer->{'stats'}->{'latency_day'}),
+ make_min_hr($remailer->{'stats'}->{'avr_latency'}, 1),
+ $remailer->{'stats'}->{'avr_reliability'} * 100;
+ };
+
+
+ write_file($filebasename, $html_template, Echolot::Config::get()->{'buildstats'}, $output) or
+ Echolot::Log::debug("writefile failed."),
+ return 0;
+ return 1;
+};
+
+
+sub build_list2($$$$$$;$) {
+ my ($rems, $type, $broken1, $broken2, $sameop, $filebasename, $html_template) = @_;
+
+ my $output = '';
+
+ $output .= sprintf "Stats-Version: 2.0\n";
+ $output .= sprintf "Generated: %s\n", make_date();
+ $output .= sprintf "%-12s Latent-Hist Latent Uptime-Hist Uptime Options\n", ($type == 1 ? 'Cypherpunk' : $type == 2 ? 'Mixmaster' : "Type $type");
+ $output .= sprintf "------------------------------------------------------------------------\n";
+
+ for my $remailer (@$rems) {
+ $output .= sprintf "%-12s %-12s %6s %-12s %5.1f%% %s\n",
+ substr($remailer->{'nick'},0,12),
+ build_list2_latencystr($remailer->{'stats'}->{'latency_day'}),
+ make_min_hr($remailer->{'stats'}->{'avr_latency'}, 0),
+ build_list2_reliabilitystr($remailer->{'stats'}->{'reliability_day'}),
+ $remailer->{'stats'}->{'avr_reliability'} * 100,
+ build_list2_capsstr($remailer->{'caps'});
+ };
+
+ $output .= sprintf "\nGroups of remailers sharing a machine or operator:\n$sameop\n" if (defined $sameop);
+ $output .= sprintf "\nBroken type-I remailer chains:\n$broken1\n" if (defined $broken1);
+ $output .= sprintf "\nBroken type-II remailer chains:\n$broken2\n" if (defined $broken2);
+
+ $output .= sprintf "\n\n\nRemailer-Capabilities:\n\n";
+ for my $remailer (sort {$a->{'caps'} cmp $b->{'caps'}} @$rems) {
+ $output .= $remailer->{'caps'}."\n" if defined $remailer->{'caps'};
+ }
+
+ write_file($filebasename, $html_template, Echolot::Config::get()->{'buildstats'}, $output) or
+ Echolot::Log::debug("writefile failed."),
+ return 0;
+ return 1;
+};
+
+sub build_clist($$$$$;$) {
+ my ($remhash, $broken1, $broken2, $sameop, $filebasename, $html_template) = @_;
+
+ my $output = '';
+
+ $output .= sprintf "Stats-Version: 2.0.1\n";
+ $output .= sprintf "Generated: %s\n", make_date();
+ $output .= sprintf "Mixmaster Latent-Hist Latent Uptime-Hist Uptime Options Type\n";
+ $output .= sprintf "------------------------------------------------------------------------------------\n";
+
+ my $all;
+ for my $type (keys %$remhash) {
+ for my $remailer (@{$remhash->{$type}}) {
+ $all->{ $remailer->{'nick'} }->{$type} = $remailer
+ };
+ };
+
+ for my $nick (sort {$a cmp $b} keys %$all) {
+ for my $type (sort {$a cmp $b} keys %{$all->{$nick}}) {
+ $output .= sprintf "%-12s %-12s %6s %-12s %5.1f%% %s %s\n",
+ $nick,
+ build_list2_latencystr($all->{$nick}->{$type}->{'stats'}->{'latency_day'}),
+ make_min_hr($all->{$nick}->{$type}->{'stats'}->{'avr_latency'}, 0),
+ build_list2_reliabilitystr($all->{$nick}->{$type}->{'stats'}->{'reliability_day'}),
+ $all->{$nick}->{$type}->{'stats'}->{'avr_reliability'} * 100,
+ build_list2_capsstr($all->{$nick}->{$type}->{'caps'}),
+ $type;
+ };
+ };
+
+ $output .= sprintf "\nGroups of remailers sharing a machine or operator:\n$sameop\n" if (defined $sameop);
+ $output .= sprintf "\nBroken type-I remailer chains:\n$broken1\n" if (defined $broken1);
+ $output .= sprintf "\nBroken type-II remailer chains:\n$broken2\n" if (defined $broken2);
+
+ $output .= sprintf "\n\n\nRemailer-Capabilities:\n\n";
+ for my $nick (sort {$a cmp $b} keys %$all) {
+ for my $type (keys %{$all->{$nick}}) {
+ $output .= $all->{$nick}->{$type}->{'caps'}."\n", last if defined $all->{$nick}->{$type}->{'caps'};
+ };
+ }
+
+ write_file($filebasename, $html_template, Echolot::Config::get()->{'buildstats'}, $output) or
+ Echolot::Log::debug("writefile failed."),
+ return 0;
+ return 1;
+};
+
+
+sub build_rems($) {
+ my ($types) = @_;
+
+ my %rems;
+ for my $remailer (Echolot::Globals::get()->{'storage'}->get_addresses()) {
+ my $addr = $remailer->{'address'};
+ my $has_type = 0;
+ for my $type (@$types) {
+ $has_type = 1, last if (Echolot::Globals::get()->{'storage'}->has_type($addr, $type));
+ };
+ next unless $has_type;
+
+ my $rem = {
+ 'stats' => calculate($addr,$types),
+ 'nick' => Echolot::Globals::get()->{'storage'}->get_nick($addr),
+ 'caps' => Echolot::Globals::get()->{'storage'}->get_capabilities($addr),
+ 'address' => $addr,
+ };
+ $rem->{'list-it'} = $remailer->{'showit'} && defined $rem->{'caps'} && ($rem->{'caps'} !~ m/\btesting\b/i);
+ $rem->{'latency'} = $rem->{'stats'}->{'avr_latency'}; # for sorting purposes only
+ $rem->{'latency'} = 9999 unless defined $rem->{'latency'};
+
+ $rems{$addr} = $rem if (defined $rem->{'stats'} && defined $rem->{'nick'} && defined $rem->{'address'} && defined $rem->{'caps'} );
+ };
+
+ my $sort_by_latency = Echolot::Config::get()->{'stats_sort_by_latency'};
+ my @rems =
+ sort {
+ - ($a->{'stats'}->{'avr_reliability'} <=> $b->{'stats'}->{'avr_reliability'}) ||
+ (($a->{'latency'} <=> $b->{'latency'}) * $sort_by_latency) ||
+ ($a->{'nick'} cmp $b->{'nick'})
+ } map { $rems{$_} } keys %rems;
+
+ return \@rems;
+};
+
+sub compress_broken_chain($@) {
+ my ($num, @list) = @_;
+
+ my %unique = ();
+ @list = sort { $a cmp $b} grep { ! $unique{$_}++; } @list;
+
+ my %bad_left;
+ my %bad_right;
+ for my $chain (@list) {
+ chomp $chain;
+ my ($left, $right) = $chain =~ m/\((\S+) \s (\S+)\)/x or
+ Echolot::Log::warn("Could not parse bad chain '$chain'."),
+ next;
+ $bad_right{$right}++;
+ $bad_right{$right} += $num if ($left eq '*');
+ $bad_left {$left }++;
+ $bad_left {$left } += $num if ($right eq '*');
+ };
+
+
+ my $threshold = $num * Echolot::Config::get()->{'chainping_allbad_factor'};
+ my @result = ();
+ for my $key (keys %bad_right) {
+ delete $bad_right{$key}, next if $bad_right{$key} < $threshold;
+ push @result, "(* $key)";
+ };
+ for my $key (keys %bad_left) {
+ delete $bad_left{$key}, next if $bad_left{$key} < $threshold;
+ push @result, "($key *)";
+ };
+
+ for my $chain (@list) {
+ chomp $chain;
+ my ($left, $right) = $chain =~ m/\((\S+) \s (\S+)\)/x or
+ # Echolot::Log::warn("Could not parse bad chain '$chain'."), -- don't warn again
+ push(@result, $chain),
+ next;
+ next if defined $bad_right{$right};
+ next if defined $bad_left {$left };
+ push(@result, $chain),
+ };
+
+ %unique = ();
+ @result = sort { $a cmp $b} grep { ! $unique{$_}++; } @result;
+
+ return @result;
+};
+
+sub find_broken_chains($$$) {
+ my ($chaintype, $rems, $hard) = @_;
+
+ if (!defined $LAST_BROKENCHAIN_RUN{$chaintype} ||
+ $LAST_BROKENCHAIN_RUN{$chaintype} < time() - Echolot::Config::get()->{'chainping_update'} ||
+ ! defined $BROKEN_CHAINS{$chaintype} ) {
+ Echolot::Log::debug ("Broken Chains $chaintype need generating."),
+ $LAST_BROKENCHAIN_RUN{$chaintype} = time();
+
+ my $pings = Echolot::Globals::get()->{'storage'}->get_chainpings($chaintype);
+ my @intensive_care = ();
+ my %remailers = map { $_->{'address'} => $_ } @$rems;
+
+ my $stats;
+ my %received;
+ my @broken_chains;
+ for my $status (qw{done out}) {
+ my $status_done = $status eq 'done';
+ my $status_out = $status eq 'out';
+ for my $ping (@{$pings->{$status}}) {
+ my $addr1 = $ping->{'addr1'};
+ my $addr2 = $ping->{'addr2'};
+ my $sent = $ping->{'sent'};
+ next if $sent < (time() - Echolot::Config::get()->{'chainping_period'});
+ next unless defined $remailers{$addr1};
+ next unless defined $remailers{$addr2};
+
+ if ($status_done) {
+ $received{$addr1.':'.$addr2.':'.$sent} = 1;
+ };
+ if ($status_out && !defined $received{$addr1.':'.$addr2.':'.$sent}) {
+ my $lat1 = $remailers{$addr1}->{'stats'}->{'avr_latency'};
+ my $lat2 = $remailers{$addr2}->{'stats'}->{'avr_latency'};
+ $lat1 = 0 unless defined $lat1;
+ $lat2 = 0 unless defined $lat2;
+ my $theoretical_lat = $lat1 + $lat2;
+ $theoretical_lat = 0 unless defined $theoretical_lat;
+ my $latency = time() - $ping->{'sent'};
+ # print ("lat helps $latency < ".int($theoretical_lat * Echolot::Config::get()->{'chainping_grace'})." $addr1 $addr2\n"),
+ next if ($latency < $theoretical_lat * Echolot::Config::get()->{'chainping_grace'});
+ };
+
+ # print "Having $addr1 $addr2 $status at $sent\n";
+ $stats->{$addr1}->{$addr2}->{$status}++;
+ };
+ };
+ # require Data::Dumper;
+ # print Data::Dumper->Dump([$stats]);
+ for my $addr1 (keys %$stats) {
+ for my $addr2 (keys %{$stats->{$addr1}}) {
+ my $theoretical_rel = $remailers{$addr1}->{'stats'}->{'avr_reliability'} *
+ $remailers{$addr2}->{'stats'}->{'avr_reliability'};
+ my $out = $stats->{$addr1}->{$addr2}->{'out'};
+ my $done = $stats->{$addr1}->{$addr2}->{'done'};
+ $done = 0 unless defined $done;
+ ($out < Echolot::Config::get()->{'chainping_minsample'} && $done == 0) and
+ push (@intensive_care, { addr1 => $addr1, addr2 => $addr2, reason => "only $out sample".($out>1?'s':'').", none returned so far" }),
+ next;
+ ($out > 0) or
+ Echolot::Log::debug("Should not devide through zero ($done/$out) for $addr1, $addr2."),
+ next;
+ my $real_rel = $done / $out;
+ # print "$addr1 $addr2 $done / $out == $real_rel ($theoretical_rel)\n";
+ next if ($real_rel > $theoretical_rel * Echolot::Config::get()->{'chainping_fudge'});
+ my $nick1 = $remailers{$addr1}->{'nick'};
+ my $nick2 = $remailers{$addr2}->{'nick'};
+ push @broken_chains,
+ { public => $remailers{$addr1}->{'list-it'} && $remailers{$addr2}->{'list-it'},
+ chain => "($nick1 $nick2)" };
+ push @intensive_care, { addr1 => $addr1, addr2 => $addr2, reason => "bad: $done/$out" };
+ };
+ };
+ $BROKEN_CHAINS{$chaintype} = \@broken_chains;
+ Echolot::Chain::set_intensive_care($chaintype, \@intensive_care);
+ } else {
+ Echolot::Log::debug ("Broken Chains $chaintype are up to date."),
+ };
+
+ my @hard = defined $hard ? (split /\n/, $hard) : ();
+ my @pub = @hard;
+ my @priv = @hard;
+ push @pub, map { $_->{'chain'} } grep { $_->{'public'} } @{ $BROKEN_CHAINS{$chaintype} };
+ push @priv, map { $_->{'chain'} } @{ $BROKEN_CHAINS{$chaintype} };
+
+ my $pub = join "\n", compress_broken_chain(scalar @$rems, @pub);
+ my $priv = join "\n", compress_broken_chain(scalar @$rems, @priv);
+
+ return ($pub, $priv);
+};
+
+sub build_lists() {
+
+ my $clist;
+ my $pubclist;
+ my $rems;
+ my $pubrems;
+
+ my %stats;
+ my %addresses;
+
+ my $hardbroken1 = Echolot::Tools::read_file( Echolot::Config::get()->{'broken1'}, 1);
+ my $hardbroken2 = Echolot::Tools::read_file( Echolot::Config::get()->{'broken2'}, 1);
+ my $sameop = Echolot::Tools::read_file( Echolot::Config::get()->{'sameop'}, 1);
+ my $pubbroken1;
+ my $pubbroken2;
+ my $privbroken1;
+ my $privbroken2;
+
+ my $mixrems = build_rems(['mix']);
+ my $cpunkrems = build_rems(['cpunk-rsa', 'cpunk-dsa', 'cpunk-clear']);
+
+ if (Echolot::Config::get()->{'do_chainpings'}) {
+ ($pubbroken1, $privbroken1) = find_broken_chains('cpunk', $cpunkrems, $hardbroken1);
+ ($pubbroken2, $privbroken2) = find_broken_chains('mix' , $mixrems , $hardbroken2);
+ } else {
+ $pubbroken1 = $privbroken1 = $hardbroken1;
+ $pubbroken2 = $privbroken2 = $hardbroken2;
+ };
+
+ unless (Echolot::Config::get()->{'show_chainpings'}) {
+ $pubbroken1 = $hardbroken1;
+ $pubbroken2 = $hardbroken2;
+ };
+
+ $rems = $mixrems;
+ $mixrems = undef;
+ @$pubrems = grep { $_->{'list-it'} } @$rems;
+ build_mlist1( $rems, $privbroken1, $privbroken2, $sameop, Echolot::Config::get()->{'private_resultdir'}.'/'.'mlist', 'mlist');
+ build_list2( $rems, 2, $privbroken1, $privbroken2, $sameop, Echolot::Config::get()->{'private_resultdir'}.'/'.'mlist2', 'mlist2');
+ build_mlist1( $pubrems, $pubbroken1, $pubbroken2, $sameop, Echolot::Config::get()->{'resultdir'}.'/'.'mlist', 'mlist');
+ build_list2( $pubrems, 2, $pubbroken1, $pubbroken2, $sameop, Echolot::Config::get()->{'resultdir'}.'/'.'mlist2', 'mlist2');
+ $stats{'mix_total'} = scalar @$pubrems;
+ $stats{'mix_98'} = scalar grep { $_->{'stats'}->{'avr_reliability'} >= 0.98 } @$pubrems;
+ $addresses{$_->{'address'}}=1 for @$pubrems;
+ if (Echolot::Config::get()->{'combined_list'}) {
+ $clist->{'mix'} = $rems;
+ $pubclist->{'mix'} = $pubrems; $pubrems = undef;
+ };
+
+ $rems = $cpunkrems;
+ $cpunkrems = undef;
+ @$pubrems = grep { $_->{'list-it'} } @$rems;
+ build_rlist1( $rems, $privbroken1, $privbroken2, $sameop, Echolot::Config::get()->{'private_resultdir'}.'/'.'rlist', 'rlist');
+ build_list2( $rems, 1, $privbroken1, $privbroken2, $sameop, Echolot::Config::get()->{'private_resultdir'}.'/'.'rlist2', 'rlist2');
+ build_rlist1( $pubrems, $pubbroken1, $pubbroken2, $sameop, Echolot::Config::get()->{'resultdir'}.'/'.'rlist', 'rlist');
+ build_list2( $pubrems, 1, $pubbroken1, $pubbroken2, $sameop, Echolot::Config::get()->{'resultdir'}.'/'.'rlist2', 'rlist2');
+ $stats{'cpunk_total'} = scalar @$pubrems;
+ $stats{'cpunk_98'} = scalar grep { $_->{'stats'}->{'avr_reliability'} >= 0.98 } @$pubrems;
+ $addresses{$_->{'address'}}=1 for @$pubrems;
+ if (Echolot::Config::get()->{'combined_list'} && ! Echolot::Config::get()->{'separate_rlists'}) {
+ $clist->{'cpunk'} = $rems;
+ $pubclist->{'cpunk'} = $pubrems; $pubrems = undef;
+ };
+
+ if (Echolot::Config::get()->{'separate_rlists'}) {
+ $rems = build_rems(['cpunk-rsa']);
+ @$pubrems = grep { $_->{'list-it'} } @$rems;
+ build_rlist1( $rems, $privbroken1, $privbroken2, $sameop, Echolot::Config::get()->{'private_resultdir'}.'/'.'rlist-rsa', 'rlist-rsa');
+ build_list2( $rems, 1, $privbroken1, $privbroken2, $sameop, Echolot::Config::get()->{'private_resultdir'}.'/'.'rlist2-rsa', 'rlist2-rsa');
+ build_rlist1( $pubrems, $pubbroken1, $pubbroken2, $sameop, Echolot::Config::get()->{'resultdir'}.'/'.'rlist-rsa', 'rlist-rsa');
+ build_list2( $pubrems, 1, $pubbroken1, $pubbroken2, $sameop, Echolot::Config::get()->{'resultdir'}.'/'.'rlist2-rsa', 'rlist2-rsa');
+ if (Echolot::Config::get()->{'combined_list'}) {
+ $clist->{'cpunk-rsa'} = $rems;
+ $pubclist->{'cpunk-rsa'} = $pubrems; $pubrems = undef;
+ };
+
+ $rems = build_rems(['cpunk-dsa']);
+ @$pubrems = grep { $_->{'list-it'} } @$rems;
+ build_rlist1( $rems, $privbroken1, $privbroken2, $sameop, Echolot::Config::get()->{'private_resultdir'}.'/'.'rlist-dsa', 'rlist-dsa');
+ build_list2( $rems, 1, $privbroken1, $privbroken2, $sameop, Echolot::Config::get()->{'private_resultdir'}.'/'.'rlist2-dsa', 'rlist2-dsa');
+ build_rlist1( $pubrems, $pubbroken1, $pubbroken2, $sameop, Echolot::Config::get()->{'resultdir'}.'/'.'rlist-dsa', 'rlist-dsa');
+ build_list2( $pubrems, 1, $pubbroken1, $pubbroken2, $sameop, Echolot::Config::get()->{'resultdir'}.'/'.'rlist2-dsa', 'rlist2-dsa');
+ if (Echolot::Config::get()->{'combined_list'}) {
+ $clist->{'cpunk-dsa'} = $rems;
+ $pubclist->{'cpunk-dsa'} = $pubrems; $pubrems = undef;
+ };
+
+ $rems = build_rems(['cpunk-clear']);
+ @$pubrems = grep { $_->{'list-it'} } @$rems;
+ build_rlist1( $rems, $privbroken1, $privbroken2, $sameop, Echolot::Config::get()->{'private_resultdir'}.'/'.'rlist-clear', 'rlist-clear');
+ build_list2( $rems, 1, $privbroken1, $privbroken2, $sameop, Echolot::Config::get()->{'private_resultdir'}.'/'.'rlist2-clear', 'rlist2-clear');
+ build_rlist1( $pubrems, $pubbroken1, $pubbroken2, $sameop, Echolot::Config::get()->{'resultdir'}.'/'.'rlist-clear', 'rlist-clear');
+ build_list2( $pubrems, 1, $pubbroken1, $pubbroken2, $sameop, Echolot::Config::get()->{'resultdir'}.'/'.'rlist2-clear', 'rlist2-clear');
+ if (Echolot::Config::get()->{'combined_list'}) {
+ $clist->{'cpunk-clear'} = $rems;
+ $pubclist->{'cpunk-clear'} = $pubrems; $pubrems = undef;
+ };
+ };
+ if (Echolot::Config::get()->{'combined_list'}) {
+ build_clist( $clist, $privbroken1, $privbroken2, $sameop, Echolot::Config::get()->{'private_resultdir'}.'/'.'clist', 'clist');
+ build_clist( $pubclist, $pubbroken1, $pubbroken2, $sameop, Echolot::Config::get()->{'resultdir'}.'/'.'clist', 'clist');
+ };
+
+ $stats{'unique_addresses'} = scalar keys %addresses;
+ Echolot::Tools::write_HTML_file(
+ Echolot::Config::get()->{'resultdir'}.'/'.Echolot::Config::get()->{'indexfilebasename'},
+ 'indexfile',
+ Echolot::Config::get()->{'buildstats'},
+ %stats );
+
+ my $file = Echolot::Config::get()->{'echolot_css'},
+ my $css;
+ {
+ local $/ = undef;
+ open(F, $file) or
+ Echolot::Log::warn("Could not open $file: $!."),
+ return 0;
+ $css = <F>;
+ close (F) or
+ Echolot::Log::warn("Cannot close $file: $!."),
+ return 0;
+ }
+ $file = Echolot::Config::get()->{'resultdir'}.'/echolot.css';
+ open(F, '>'.$file) or
+ Echolot::Log::warn("Cannot open $file: $!."),
+ return 0;
+ print F $css or
+ Echolot::Log::warn("Cannot print to $file: $!."),
+ return 0;
+ close (F) or
+ Echolot::Log::warn("Cannot close $file: $!."),
+ return 0;
+
+};
+
+
+sub build_mixring() {
+ my @filenames;
+
+ my $filename = Echolot::Config::get()->{'resultdir'}.'/pubring.mix';
+ push @filenames, $filename;
+ open(F, '>'.$filename) or
+ Echolot::Log::warn("Cannot open $filename: $!."),
+ return 0;
+ $filename = Echolot::Config::get()->{'resultdir'}.'/type2.list';
+ push @filenames, $filename;
+ open(T2L, '>'.$filename) or
+ Echolot::Log::warn("Cannot open $filename: $!."),
+ return 0;
+ $filename = Echolot::Config::get()->{'private_resultdir'}.'/pubring.mix';
+ push @filenames, $filename;
+ open(F_PRIV, '>'.$filename) or
+ Echolot::Log::warn("Cannot open $filename: $!."),
+ return 0;
+ $filename = Echolot::Config::get()->{'private_resultdir'}.'/type2.list';
+ push @filenames, $filename;
+ open(T2L_PRIV, '>'.$filename) or
+ Echolot::Log::warn("Cannot open $filename: $!."),
+ return 0;
+
+ my $data;
+ for my $remailer (Echolot::Globals::get()->{'storage'}->get_addresses()) {
+ my $addr = $remailer->{'address'};
+ next unless Echolot::Globals::get()->{'storage'}->has_type($addr, 'mix');
+
+ my %key;
+ for my $keyid (Echolot::Globals::get()->{'storage'}->get_keys($addr, 'mix')) {
+ my %new_key = Echolot::Globals::get()->{'storage'}->get_key($addr, 'mix', $keyid);
+
+ if (!defined $key{'last_update'} || $key{'last_update'} < $new_key{'last_update'} ) {
+ %key = %new_key;
+ };
+ };
+
+ my $caps = Echolot::Globals::get()->{'storage'}->get_capabilities($addr);
+ $key{'list-it'} = $remailer->{'showit'} && defined $caps && ($caps !~ m/\btesting\b/i);
+ if ( defined Echolot::Globals::get()->{'storage'}->get_nick($addr) ) {
+ $data->{$key{'summary'}} = \%key;
+ $data->{$key{'summary'}} = \%key;
+ };
+ };
+
+ for my $indx (sort {$a cmp $b} keys %$data) {
+ my $key = $data->{$indx};
+ if ($key->{'list-it'}) {
+ print F $key->{'summary'}."\n\n";
+ print F $key->{'key'},"\n\n";
+ print T2L $key->{'summary'},"\n";
+ };
+ print F_PRIV $key->{'summary'}."\n\n";
+ print F_PRIV $key->{'key'},"\n\n";
+ print T2L_PRIV $key->{'summary'},"\n";
+ };
+
+ close(F);
+ close(T2L);
+ close(F_PRIV);
+ close(T2L_PRIV);
+
+ for my $filename (@filenames) {
+ Echolot::Tools::write_meta_information($filename,
+ Expires => time + Echolot::Config::get()->{'buildkeys'}) or
+ Echolot::Log::debug ("Error while writing meta information for $filename."),
+ return 0;
+ };
+};
+
+
+
+sub build_pgpring_type($$$$) {
+ my ($type, $GnuPG, $keyring, $keyids) = @_;
+
+ for my $remailer (Echolot::Globals::get()->{'storage'}->get_addresses()) {
+ my $addr = $remailer->{'address'};
+ next unless Echolot::Globals::get()->{'storage'}->has_type($addr, $type);
+
+ my %key;
+ my $final_keyid;
+ for my $keyid (Echolot::Globals::get()->{'storage'}->get_keys($addr, $type)) {
+ my %new_key = Echolot::Globals::get()->{'storage'}->get_key($addr, $type, $keyid);
+
+ if (!defined $key{'last_update'} || $key{'last_update'} < $new_key{'last_update'} ) {
+ %key = %new_key;
+ $final_keyid = $keyid;
+ };
+ };
+
+ # only if we have a conf
+ if ( defined Echolot::Globals::get()->{'storage'}->get_nick($addr) ) {
+ my ( $stdin_fh, $stdout_fh, $stderr_fh, $status_fh, $handles ) = Echolot::Tools::make_gpg_fds();
+ my $pid = $GnuPG->wrap_call(
+ commands => [ '--import' ],
+ command_args => [qw{--no-options --no-secmem-warning --no-default-keyring --fast-list-mode --keyring}, $keyring, '--', '-' ],
+ handles => $handles );
+ my ($stdout, $stderr, $status) = Echolot::Tools::readwrite_gpg($key{'key'}, $stdin_fh, $stdout_fh, $stderr_fh, $status_fh);
+ waitpid $pid, 0;
+
+ ($stdout eq '') or
+ Echolot::Log::info("GnuPG returned something in stdout '$stdout' while adding key for '$addr': So what?");
+ # See DETAIL.gz in GnuPG's doc directory for syntax of GnuPG status
+ my ($count, $count_imported) = $status =~ /^\[GNUPG:\] IMPORT_RES (\d+) \d+ (\d+)/m;
+ if ($count_imported > 1) {
+ Echolot::Log::info("GnuPG status '$status' indicates more than one key for '$addr' imported. Ignoring.");
+ } elsif ($count_imported < 1) {
+ Echolot::Log::info("GnuPG status '$status' didn't indicate key for '$addr' was imported correctly. Ignoring.");
+ };
+ my $caps = Echolot::Globals::get()->{'storage'}->get_capabilities($addr);
+ $keyids->{$final_keyid} = $remailer->{'showit'} && defined $caps && ($caps !~ m/\btesting\b/i);
+ };
+ };
+
+ return 1;
+};
+
+sub build_pgpring_export($$$$) {
+ my ($GnuPG, $keyring, $file, $keyids) = @_;
+
+ my ( $stdin_fh, $stdout_fh, $stderr_fh, $status_fh, $handles ) = Echolot::Tools::make_gpg_fds();
+ my $pid = $GnuPG->wrap_call(
+ commands => [ '--export' ],
+ command_args => [qw{--no-options --no-secmem-warning --no-default-keyring --keyring}, $keyring, @$keyids ],
+ handles => $handles );
+ my ($stdout, $stderr, $status) = Echolot::Tools::readwrite_gpg('', $stdin_fh, $stdout_fh, $stderr_fh, $status_fh);
+ waitpid $pid, 0;
+
+ open (F, ">$file") or
+ Echolot::Log::warn ("Cannot open '$file': $!."),
+ return 0;
+ print F $stdout;
+ close F;
+
+ Echolot::Tools::write_meta_information($file,
+ Expires => time + Echolot::Config::get()->{'buildkeys'}) or
+ Echolot::Log::debug ("Error while writing meta information for $file."),
+ return 0;
+
+ return 1;
+};
+
+sub build_pgpring() {
+ my $GnuPG = new GnuPG::Interface;
+ $GnuPG->call( Echolot::Config::get()->{'gnupg'} ) if (Echolot::Config::get()->{'gnupg'});
+ $GnuPG->options->hash_init(
+ armor => 1,
+ homedir => Echolot::Config::get()->{'gnupghome'} );
+ $GnuPG->options->meta_interactive( 0 );
+
+ my $keyring = Echolot::Config::get()->{'tmpdir'}.'/'.
+ Echolot::Globals::get()->{'hostname'}.".".time.'.'.$PROCESS_ID.'_'.Echolot::Globals::get()->{'internalcounter'}++.'.keyring';
+
+
+ my $keyids = {};
+ build_pgpring_type('cpunk-rsa', $GnuPG, $keyring, $keyids) or
+ Echolot::Log::debug("build_pgpring_type failed."),
+ return undef;
+
+ build_pgpring_export($GnuPG, $keyring, Echolot::Config::get()->{'resultdir'}.'/pgp-rsa.asc', [ grep {$keyids->{$_}} keys %$keyids ]) or
+ Echolot::Log::debug("build_pgpring_export failed."),
+ return undef;
+
+ build_pgpring_export($GnuPG, $keyring, Echolot::Config::get()->{'private_resultdir'}.'/pgp-rsa.asc', [ keys %$keyids ]) or
+ Echolot::Log::debug("build_pgpring_export failed."),
+ return undef;
+
+ build_pgpring_type('cpunk-dsa', $GnuPG, $keyring, $keyids) or
+ Echolot::Log::debug("build_pgpring_type failed."),
+ return undef;
+
+ build_pgpring_export($GnuPG, $keyring, Echolot::Config::get()->{'resultdir'}.'/pgp-all.asc', [ grep {$keyids->{$_}} keys %$keyids ]) or
+ Echolot::Log::debug("build_pgpring_export failed."),
+ return undef;
+
+ build_pgpring_export($GnuPG, $keyring, Echolot::Config::get()->{'private_resultdir'}.'/pgp-all.asc', [ keys %$keyids ]) or
+ Echolot::Log::debug("build_pgpring_export failed."),
+ return undef;
+
+
+ unlink ($keyring) or
+ Echolot::Log::warn("Cannot unlink tmp keyring '$keyring'."),
+ return undef;
+ unlink ($keyring.'~'); # gnupg does those evil backups
+};
+
+sub build_stats() {
+ $STATS_DAYS = Echolot::Config::get()->{'stats_days'};
+ $SECONDS_PER_DAY = Echolot::Config::get()->{'seconds_per_day'};
+ $WEIGHT = Echolot::Config::get()->{'pings_weight'};
+ build_lists();
+};
+sub build_keys() {
+ build_mixring();
+ build_pgpring();
+};
+
+1;
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Storage/File.pm b/trunk/Echolot/Storage/File.pm
new file mode 100644
index 0000000..1f6d21d
--- /dev/null
+++ b/trunk/Echolot/Storage/File.pm
@@ -0,0 +1,1880 @@
+package Echolot::Storage::File;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Storage::File - Storage backend for echolot
+
+=head1 DESCRIPTION
+
+This package provides several functions for data storage for echolot.
+
+=over
+
+=cut
+
+use strict;
+use Data::Dumper;
+use IO::Handle;
+use English;
+use Carp;
+use Fcntl ':flock'; # import LOCK_* constants
+#use Fcntl ':seek'; # import SEEK_* constants
+use POSIX; # import SEEK_* constants (older perls don't have SEEK_ in Fcntl)
+use Echolot::Tools;
+use Echolot::Log;
+
+
+
+my $CONSTANTS = {
+ 'metadatafile' => 'metadata'
+};
+
+delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
+
+my $METADATA_VERSION = 1;
+
+
+=item B<new> (I<%args>)
+
+Creates a new storage backend object.
+args keys:
+
+=over
+
+=item I<datadir>
+
+The basedir where this module may store it's configuration and pinging
+data.
+
+=back
+
+=cut
+sub new {
+ my ($class, %params) = @_;
+ my $self = {};
+ bless $self, $class;
+
+ $self->{'METADATA_FILE_IS_NEW'} = 0;
+
+ defined($params{'datadir'}) or
+ confess ('No datadir option passed to new');
+ $self->{'datadir'} = $params{'datadir'};
+ $self->{'DELAY_COMMIT'} = 0;
+
+ $self->delay_commit();
+ $self->metadata_open() or
+ confess ('Opening Metadata failed. Exiting');
+ $self->metadata_read() or
+ confess ('Reading Metadata from Storage failed. Exiting');
+ $self->pingdata_open() or
+ confess ('Opening Ping files failed. Exiting');
+ $self->chainpingdata_open() or
+ confess ('Opening Ping files failed. Exiting');
+ $self->enable_commit();
+
+ return $self;
+};
+
+=item $storage->B<commit>( )
+
+Write metadata unless B<delay_commt> is set.
+
+=cut
+sub commit($) {
+ my ($self) = @_;
+
+ if ($self->{'DELAY_COMMIT'}) {
+ $self->{'COMMIT_PENDING'} = 1;
+ return;
+ };
+ $self->metadata_write();
+ $self->{'COMMIT_PENDING'} = 0;
+};
+
+=item $storage->B<delay_commit>( )
+
+Increase B<delay_commit> by one.
+
+=cut
+sub delay_commit($) {
+ my ($self) = @_;
+
+ $self->{'DELAY_COMMIT'}++;
+};
+
+=item $storage->B<enable_commit>( I<$set_> )
+
+Decrease B<delay_commit> by one and call C<commit> if B<delay_commit> is zero
+and I<$set_pending> is true.
+
+=cut
+sub enable_commit($;$) {
+ my ($self, $set_pending) = @_;
+
+ $self->{'DELAY_COMMIT'}--;
+ $self->commit() if (($self->{'COMMIT_PENDING'} || (defined $set_pending && $set_pending)) && ! $self->{'DELAY_COMMIT'});
+};
+
+=item $storage->B<finish>( )
+
+Shut down cleanly.
+
+=cut
+sub finish($) {
+ my ($self) = @_;
+
+ $self->pingdata_close();
+ $self->chainpingdata_close();
+ $self->metadata_write();
+ $self->metadata_close();
+};
+
+
+
+
+=item $storage->B<metadata_open>( )
+
+Open metadata.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub metadata_open($) {
+ my ($self) = @_;
+
+ $self->{'METADATA_FH'} = new IO::Handle;
+ my $filename = $self->{'datadir'} .'/'. $CONSTANTS->{'metadatafile'};
+
+ if ( -e $filename ) {
+ open($self->{'METADATA_FH'}, '+<' . $filename) or
+ Echolot::Log::warn("Cannot open $filename for reading: $!."),
+ return undef;
+ } else {
+ $self->{'METADATA_FILE_IS_NEW'} = 1;
+ open($self->{'METADATA_FH'}, '+>' . $filename) or
+ Echolot::Log::warn("Cannot open $filename for reading: $!."),
+ return undef;
+ };
+ flock($self->{'METADATA_FH'}, LOCK_EX) or
+ Echolot::Log::warn("Cannot get exclusive lock on $filename: $!."),
+ return undef;
+ return 1;
+};
+
+=item $storage->B<metadata_close>( )
+
+Close metadata.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub metadata_close($) {
+ my ($self) = @_;
+
+ flock($self->{'METADATA_FH'}, LOCK_UN) or
+ Echolot::Log::warn("Error when releasing lock on metadata file: $!."),
+ return undef;
+ close($self->{'METADATA_FH'}) or
+ Echolot::Log::warn("Error when closing metadata file: $!."),
+ return undef;
+ return 1;
+};
+
+
+=item $storage->B<metadata_read>( )
+
+Write metadata.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub metadata_read($) {
+ my ($self) = @_;
+
+ if ($self->{'METADATA_FILE_IS_NEW'}) {
+ $self->{'METADATA'}->{'version'} = $METADATA_VERSION;
+ $self->{'METADATA'}->{'addresses'} = {};
+ $self->{'METADATA'}->{'remailers'} = {};
+
+ $self->{'METADATA_FILE_IS_NEW'} = 0;
+ $self->commit();
+ } else {
+ $self->{'METADATA'} = ();
+ seek($self->{'METADATA_FH'}, 0, SEEK_SET) or
+ Echolot::Log::warn("Cannot seek to start of metadata file: $!."),
+ return 0;
+ {
+ local $/ = undef;
+ my $fh = $self->{'METADATA_FH'};
+ my $metadata_code = <$fh>;
+ ($metadata_code) = $metadata_code =~ /^(.*)$/s;
+ my $METADATA;
+ eval ($metadata_code);
+ $self->{'METADATA'} = $METADATA;
+ };
+ $EVAL_ERROR and
+ confess("Error when reading from metadata file: $EVAL_ERROR"),
+ return undef;
+
+ defined($self->{'METADATA'}->{'version'}) or
+ confess("Stored data lacks version header"),
+ return undef;
+ ($self->{'METADATA'}->{'version'} == ($METADATA_VERSION)) or
+ Echolot::Log::warn("Metadata version mismatch ($self->{'METADATA'}->{'version'} vs. $METADATA_VERSION)."),
+ return undef;
+ };
+
+ defined($self->{'METADATA'}->{'secret'}) or
+ $self->{'METADATA'}->{'secret'} = Echolot::Tools::make_random ( 16, armor => 1 ),
+ $self->commit();
+
+ return 1;
+};
+
+=item $storage->B<metadata_write>( )
+
+Write metadata.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub metadata_write($) {
+ my ($self) = @_;
+
+ my $data = Data::Dumper->Dump( [ $self->{'METADATA'} ], [ 'METADATA' ] );
+ my $fh = $self->{'METADATA_FH'};
+
+ seek($fh, 0, SEEK_SET) or
+ Echolot::Log::warn("Cannot seek to start of metadata file: $!."),
+ return undef;
+ truncate($fh, 0) or
+ Echolot::Log::warn("Cannot truncate metadata file to zero length: $!."),
+ return undef;
+ print($fh "# vim:set syntax=perl:\n") or
+ Echolot::Log::warn("Error when writing to metadata file: $!."),
+ return undef;
+ print($fh $data) or
+ Echolot::Log::warn("Error when writing to metadata file: $!."),
+ return undef;
+ $fh->flush();
+
+ return 1;
+};
+
+=item $storage->B<metadata_backup>( )
+
+Rotate metadata files and create a backup.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub metadata_backup($) {
+ my ($self) = @_;
+
+ my $filename = $self->{'datadir'} .'/'. $CONSTANTS->{'metadatafile'};
+ for (my $i=Echolot::Config::get()->{'metadata_backup_count'} - 1; $i>=0; $i--) {
+ rename ($filename.'.'.($i) , $filename.'.'.($i+1));
+ rename ($filename.'.'.($i).'.gz', $filename.'.'.($i+1).'.gz');
+ };
+ $filename .= '.1';
+
+
+ my $data = Data::Dumper->Dump( [ $self->{'METADATA'} ], [ 'METADATA' ] );
+ my $fh = new IO::Handle;
+ open ($fh, '>'.$filename) or
+ Echolot::Log::warn("Cannot open $filename for writing: $!."),
+ return undef;
+ print($fh "# vim:set syntax=perl:\n") or
+ Echolot::Log::warn("Error when writing to metadata file: $!."),
+ return undef;
+ print($fh $data) or
+ Echolot::Log::warn("Error when writing to metadata file: $!."),
+ return undef;
+ $fh->flush();
+ close($fh) or
+ Echolot::Log::warn("Error when closing metadata file: $!."),
+ return undef;
+
+ if (Echolot::Config::get()->{'gzip'}) {
+ system(Echolot::Config::get()->{'gzip'}, $filename) and
+ Echolot::Log::warn("Gziping $filename failed."),
+ return undef;
+ };
+
+ return 1;
+};
+
+
+
+
+=item $storage->B<pingdata_open_one>( I<$remailer_addr>, I<$type>, I<$key> )
+
+Open the pingdata file for the I<$remailer_addr>, I<$type>, and I<$key>.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub pingdata_open_one($$$$) {
+ my ($self, $remailer_addr, $type, $key) = @_;
+
+ defined ($self->{'METADATA'}->{'remailers'}->{$remailer_addr}) or
+ Echolot::Log::cluck ("$remailer_addr does not exist in Metadata."),
+ return undef;
+ defined ($self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}) or
+ Echolot::Log::cluck ("$remailer_addr has no keys in Metadata."),
+ return undef;
+ defined ($self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type}) or
+ Echolot::Log::cluck ("$remailer_addr type $type does not exist in Metadata."),
+ return undef;
+ defined ($self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type}->{$key}) or
+ Echolot::Log::cluck ("$remailer_addr type $type key $key does not exist in Metadata."),
+ return undef;
+
+
+ my $basename = $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type}->{$key}->{'stats'};
+ defined($basename) or
+ $basename = $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type}->{$key}->{'stats'} = $remailer_addr.'.'.$type.'.'.$key.'.'.time.'.'.$PROCESS_ID.'_'.Echolot::Globals::get()->{'internalcounter'}++,
+ $self->commit();
+
+ my $filename = $self->{'datadir'} .'/'. $basename;
+
+ for my $direction ('out', 'done') {
+ my $fh = new IO::Handle;
+ if ( -e $filename.'.'.$direction ) {
+ open($fh, '+<' . $filename.'.'.$direction) or
+ Echolot::Log::warn("Cannot open $filename.$direction for reading: $!."),
+ return undef;
+ $self->{'PING_FHS'}->{$remailer_addr}->{$type}->{$key}->{$direction} = $fh;
+ } else {
+ open($fh, '+>' . $filename.'.'.$direction) or
+ Echolot::Log::warn("Cannot open $filename.$direction for reading: $!."),
+ return undef;
+ $self->{'PING_FHS'}->{$remailer_addr}->{$type}->{$key}->{$direction} = $fh;
+ };
+ flock($fh, LOCK_EX) or
+ Echolot::Log::warn("Cannot get exclusive lock on $remailer_addr $type $key $direction pings: $!."),
+ return undef;
+ };
+
+ return 1;
+};
+
+=item $storage->B<pingdata_open>( )
+
+Open all pingdata files.
+
+Returns 1.
+
+=cut
+sub pingdata_open($) {
+ my ($self) = @_;
+
+ for my $remailer_addr ( keys %{$self->{'METADATA'}->{'remailers'}} ) {
+ for my $type ( keys %{$self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}} ) {
+ for my $key ( keys %{$self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type}} ) {
+ $self->pingdata_open_one($remailer_addr, $type, $key);
+ };
+ };
+ };
+ return 1;
+};
+
+=item $storage->B<get_ping_fh>( I<$remailer_addr>, I<$type>, I<$key>, I<$direction>, I<$oknodo> )
+
+Return the FH for the pingdata file of I<$remailer_addr>, I<$type>, I<$key>, and I<$direction>.
+
+If $<oknodo> is set, the absense of a defined filehandle does not cause it to
+be opened/created. Instead -1 is returned.
+
+Returns undef on error;
+
+=cut
+sub get_ping_fh($$$$$;$) {
+ my ($self, $remailer_addr, $type, $key, $direction, $oknodo) = @_;
+
+ defined ($self->{'METADATA'}->{'addresses'}->{$remailer_addr}) or
+ Echolot::Log::cluck("$remailer_addr does not exist in Metadata."),
+ return undef;
+
+ my $fh = $self->{'PING_FHS'}->{$remailer_addr}->{$type}->{$key}->{$direction};
+
+ unless (defined $fh) {
+ return -1 if (defined $oknodo && $oknodo);
+
+ $self->pingdata_open_one($remailer_addr, $type, $key),
+ $fh = $self->{'PING_FHS'}->{$remailer_addr}->{$type}->{$key}->{$direction};
+ defined ($fh) or
+ Echolot::Log::warn ("$remailer_addr; type=$type; key=$key has no assigned filehandle for $direction pings."),
+ return undef;
+ }
+
+ return $fh;
+};
+
+=item $storage->B<pingdata_close_one>( I<$remailer_addr>, I<$type>, I<$key> )
+
+Close the pingdata file for the I<$remailer_addr>, I<$type>, and I<$key>.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub pingdata_close_one($$$$;$) {
+ my ($self, $remailer_addr, $type, $key, $delete) = @_;
+
+ for my $direction ( keys %{$self->{'PING_FHS'}->{$remailer_addr}->{$type}->{$key}} ) {
+ my $fh = $self->{'PING_FHS'}->{$remailer_addr}->{$type}->{$key}->{$direction};
+
+ flock($fh, LOCK_UN) or
+ Echolot::Log::warn("Error when releasing lock on $remailer_addr type $type key $key direction $direction pings: $!."),
+ return undef;
+ close ($fh) or
+ Echolot::Log::warn("Error when closing $remailer_addr type $type key $key direction $direction pings: $!."),
+ return undef;
+
+ if ((defined $delete) && ($delete eq 'delete')) {
+ my $basename = $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type}->{$key}->{'stats'};
+ my $filename = $self->{'datadir'} .'/'. $basename;
+ unlink ($filename.'.'.$direction) or
+ carp ("Cannot unlink $filename.'.'.$direction: $!");
+ };
+ };
+
+ delete $self->{'PING_FHS'}->{$remailer_addr}->{$type}->{$key};
+
+ delete $self->{'PING_FHS'}->{$remailer_addr}->{$type}
+ unless (scalar keys %{$self->{'PING_FHS'}->{$remailer_addr}->{$type}});
+ delete $self->{'PING_FHS'}->{$remailer_addr}
+ unless (scalar keys %{$self->{'PING_FHS'}->{$remailer_addr}});
+
+
+ return 1;
+};
+
+=item $storage->B<pingdata_close>( )
+
+Close all pingdata files.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub pingdata_close($) {
+ my ($self) = @_;
+
+ for my $remailer_addr ( keys %{$self->{'PING_FHS'}} ) {
+ for my $type ( keys %{$self->{'PING_FHS'}->{$remailer_addr}} ) {
+ for my $key ( keys %{$self->{'PING_FHS'}->{$remailer_addr}->{$type}} ) {
+ $self->pingdata_close_one($remailer_addr, $type, $key) or
+ Echolot::Log::debug("Error when calling pingdata_close_one with $remailer_addr type $type key $key."),
+ return undef;
+ };
+ };
+ };
+ return 1;
+};
+
+=item $storage->B<get_pings>( I<$remailer_addr>, I<$type>, I<$key>, I<$direction> )
+
+Return an array of ping data for I<$remailer_addr>, I<$type>, I<$key>, and I<$direction>.
+
+If direction is B<out> then it's an array of scalar (the send timestamps).
+
+If direction is B<done> then it's an array of array references each having two
+items: the send time and the latency.
+
+Returns undef on error;
+
+=cut
+sub get_pings($$$$$) {
+ my ($self, $remailer_addr, $type, $key, $direction) = @_;
+
+ my @pings;
+
+ my $fh = $self->get_ping_fh($remailer_addr, $type, $key, $direction, 1);
+ (defined $fh) or
+ Echolot::Log::cluck ("$remailer_addr; type=$type; key=$key has no assigned filehandle for out pings.");
+ ($fh == -1) and
+ Echolot::Log::info ("$remailer_addr; type=$type; key=$key has no assigned filehandle for $direction pings (key has expired, or not available yet)."),
+ return ();
+
+ seek($fh, 0, SEEK_SET) or
+ Echolot::Log::warn("Cannot seek to start of $remailer_addr type $type key $key direction $direction pings: $! ($fh)."),
+ return undef;
+
+ if ($direction eq 'out') {
+ @pings = map {chomp; $_; } <$fh>;
+ } elsif ($direction eq 'done') {
+ @pings = map {chomp; my @arr = split (/\s+/, $_, 2); \@arr; } <$fh>;
+ } else {
+ confess("What the hell am I doing here? $remailer_addr; $type; $key; $direction"),
+ return undef;
+ };
+ return @pings;
+};
+
+
+
+
+
+=item $storage->B<register_pingout>( I<$remailer_addr>, I<$type>, I<$key>, I<$sent_time> )
+
+Register a ping sent to I<$remailer_addr>, I<$type>, I<$key> and I$<sent_time>.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub register_pingout($$$$$) {
+ my ($self, $remailer_addr, $type, $key, $sent_time) = @_;
+
+ my $fh = $self->get_ping_fh($remailer_addr, $type, $key, 'out') or
+ Echolot::Log::cluck ("$remailer_addr; type=$type; key=$key has no assigned filehandle for out pings."),
+ return undef;
+
+ seek($fh, 0, SEEK_END) or
+ Echolot::Log::warn("Cannot seek to end of $remailer_addr; type=$type; key=$key; out pings: $!."),
+ return undef;
+ print($fh $sent_time."\n") or
+ Echolot::Log::warn("Error when writing to $remailer_addr; type=$type; key=$key; out pings: $!."),
+ return undef;
+ $fh->flush();
+ Echolot::Log::debug("registering pingout for $remailer_addr ($type; $key).");
+
+ return 1;
+};
+
+=item $storage->B<register_pingdone>( I<$remailer_addr>, I<$type>, I<$key>, I<$sent_time>, I<$latency> )
+
+Register that the ping sent to I<$remailer_addr>, I<$type>, I<$key> at
+I$<sent_time> has returned with latency I<$latency>.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub register_pingdone($$$$$$) {
+ my ($self, $remailer_addr, $type, $key, $sent_time, $latency) = @_;
+
+ defined ($self->{'METADATA'}->{'addresses'}->{$remailer_addr}) or
+ Echolot::Log::warn ("$remailer_addr does not exist in Metadata."),
+ return undef;
+
+ my @outpings = $self->get_pings($remailer_addr, $type, $key, 'out');
+ my $origlen = scalar (@outpings);
+ @outpings = grep { $_ != $sent_time } @outpings;
+ ($origlen == scalar (@outpings)) and
+ Echolot::Log::info("No ping outstanding for $remailer_addr, $key, ".(scalar localtime $sent_time)."."),
+ return 1;
+
+ # write ping to done
+ my $fh = $self->get_ping_fh($remailer_addr, $type, $key, 'done') or
+ Echolot::Log::cluck ("$remailer_addr; type=$type; key=$key has no assigned filehandle for done pings."),
+ return undef;
+ seek($fh, 0, SEEK_END) or
+ Echolot::Log::warn("Cannot seek to end of $remailer_addr done pings: $!."),
+ return undef;
+ print($fh $sent_time." ".$latency."\n") or
+ Echolot::Log::warn("Error when writing to $remailer_addr done pings: $!."),
+ return undef;
+ $fh->flush();
+
+ # rewrite outstanding pings
+ $fh = $self->get_ping_fh($remailer_addr, $type, $key, 'out') or
+ Echolot::Log::cluck ("$remailer_addr; type=$type; key=$key has no assigned filehandle for out pings."),
+ return undef;
+ seek($fh, 0, SEEK_SET) or
+ Echolot::Log::warn("Cannot seek to start of outgoing pings file for remailer $remailer_addr; key=$key: $!."),
+ return undef;
+ truncate($fh, 0) or
+ Echolot::Log::warn("Cannot truncate outgoing pings file for remailer $remailer_addr; key=$key file to zero length: $!."),
+ return undef;
+ print($fh (join "\n", @outpings), (scalar @outpings ? "\n" : '') ) or
+ Echolot::Log::warn("Error when writing to outgoing pings file for remailer $remailer_addr; key=$key file: $!."),
+ return undef;
+ $fh->flush();
+ Echolot::Log::debug("registering pingdone from ".(scalar localtime $sent_time)." with latency $latency for $remailer_addr ($type; $key).");
+
+ return 1;
+};
+
+
+
+
+
+
+=item $storage->B<chainpingdata_open_one>( I<$chaintype> )
+
+Open the pingdata file for I<$chaintype> type chain pings.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub chainpingdata_open_one($$) {
+ my ($self, $type) = @_;
+
+ my $filename = $self->{'datadir'} .'/chainpings.'.$type;
+
+ for my $direction ('out', 'done') {
+ my $fh = new IO::Handle;
+ if ( -e $filename.'.'.$direction ) {
+ open($fh, '+<' . $filename.'.'.$direction) or
+ Echolot::Log::warn("Cannot open $filename.$direction for reading: $!."),
+ return undef;
+ $self->{'CHAINPING_FHS'}->{$type}->{$direction} = $fh;
+ } else {
+ open($fh, '+>' . $filename.'.'.$direction) or
+ Echolot::Log::warn("Cannot open $filename.$direction for reading: $!."),
+ return undef;
+ $self->{'CHAINPING_FHS'}->{$type}->{$direction} = $fh;
+ };
+ flock($fh, LOCK_EX) or
+ Echolot::Log::warn("Cannot get exclusive lock on $filename.$direction pings: $!."),
+ return undef;
+ };
+
+ return 1;
+};
+
+=item $storage->B<chainpingdata_open>( )
+
+Open all chainpingdata files.
+
+Returns 1.
+
+=cut
+sub chainpingdata_open($) {
+ my ($self) = @_;
+
+ for my $type ( keys %{Echolot::Config::get()->{'which_chainpings'}} ) {
+ $self->chainpingdata_open_one($type);
+ };
+
+ return 1;
+};
+
+
+=item $storage->B<get_chainping_fh>( I<$type>, I<$direction> )
+
+Return the FH for the chainpingdata file of I<$type>, and I<$direction>.
+
+Returns undef on error;
+
+=cut
+sub get_chainping_fh($$$) {
+ my ($self, $type, $direction) = @_;
+
+ my $fh = $self->{'CHAINPING_FHS'}->{$type}->{$direction};
+
+ defined ($fh) or
+ $self->chainpingdata_open_one($type),
+ $fh = $self->{'CHAINPING_FHS'}->{$type}->{$direction};
+ defined ($fh) or
+ Echolot::Log::warn ("chainping $type has no assigned filehandle for $direction chainpings."),
+ return undef;
+
+ return $fh;
+};
+
+=item $storage->B<chainpingdata_close_one>( I<$type> )
+
+Close the chainpingdata file for I<$type>.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub chainpingdata_close_one($) {
+ my ($self, $type) = @_;
+
+ for my $direction ( keys %{$self->{'CHAINPING_FHS'}->{$type}} ) {
+ my $fh = $self->{'CHAINPING_FHS'}->{$type}->{$direction};
+
+ flock($fh, LOCK_UN) or
+ Echolot::Log::warn("Error when releasing lock on $type direction $direction chainpings: $!."),
+ return undef;
+ close ($fh) or
+ Echolot::Log::warn("Error when closing $type direction $direction chainpings: $!."),
+ return undef;
+ };
+
+ delete $self->{'CHAINPING_FHS'}->{$type};
+
+ return 1;
+};
+
+=item $storage->B<chainpingdata_close>( )
+
+Close all chainpingdata files.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub chainpingdata_close($) {
+ my ($self) = @_;
+
+ for my $type ( keys %{$self->{'CHAINPING_FHS'}} ) {
+ $self->chainpingdata_close_one($type) or
+ Echolot::Log::debug("Error when calling chainpingdata_close_one with type $type."),
+ return undef;
+ };
+ return 1;
+};
+
+
+
+=item $storage->B<get_chainpings>( I<$chaintype> )
+
+Return chainping data for I<$chaintype>.
+
+The result is a reference to a hash having two entries: out and done.
+
+Each of them is a reference to an array of single pings. Each ping is a hash
+reference with the hash having the keys B<sent>, B<addr1>, B<type1>, B<key1>,
+B<addr2>, B<type2>, B<key2>, and in case of received pings B<lat>.
+
+Out currently includes all sent pings - also those that allready arrived.
+This is different from the get_pings() function above.
+
+Returns undef on error.
+
+=cut
+sub get_chainpings($$) {
+ my ($self, $chaintype) = @_;
+
+ my $fh = $self->get_chainping_fh($chaintype, 'out') or
+ Echolot::Log::warn ("have no assigned filehandle for $chaintype out chainpings."),
+ return undef;
+ seek($fh, 0, SEEK_SET) or
+ Echolot::Log::warn("Cannot seek to start of $chaintype out chainpings $!."),
+ return undef;
+ my @out =
+ map {
+ chomp;
+ my @a = split;
+ Echolot::Log::warn("'$_' has not 7 fields") if (scalar @a < 7);
+ { sent => $a[0],
+ addr1 => $a[1],
+ type1 => $a[2],
+ key1 => $a[3],
+ addr2 => $a[4],
+ type2 => $a[5],
+ key2 => $a[6]
+ }
+ } <$fh>;
+ my %sent = map {
+ my $a = $_;
+ my $key = join (' ', map ({ $a->{$_} } qw{sent addr1 type1 key1 addr2 type2 key2}));
+ $key => 1
+ } @out;
+
+ $fh = $self->get_chainping_fh($chaintype, 'done') or
+ Echolot::Log::warn ("assigned filehandle for $chaintype done chainpings."),
+ return undef;
+ seek($fh, 0, SEEK_SET) or
+ Echolot::Log::warn("Cannot seek to start of $chaintype done chainpings $!."),
+ return undef;
+ my @done =
+ grep {
+ # Only list things that actually got sent - and only once
+ my $a = $_;
+ my $key = join (' ', map ({ $a->{$_} } qw{sent addr1 type1 key1 addr2 type2 key2}));
+ my $exists = exists $sent{$key};
+ delete $sent{$key};
+ $exists
+ }
+ map {
+ chomp;
+ my @a = split;
+ { sent => $a[0],
+ addr1 => $a[1],
+ type1 => $a[2],
+ key1 => $a[3],
+ addr2 => $a[4],
+ type2 => $a[5],
+ key2 => $a[6],
+ lat => $a[7]
+ }
+ } <$fh>;
+
+ return {
+ out => \@out,
+ done => \@done
+ };
+};
+
+
+=item $storage->B<register_chainpingout>( I<$chaintype>, I<$addr1>, I<$type1>, I<$key1>, I<$addr2>, I<$type2>, I<$key2>, I<$sent_time> >
+
+Register a chain ping of type I<$chaintype> sent through I<$addr1> (I<$type1>, I<$key1>)
+and I<$addr2> (I<$type2>, I<$key2>) at I$<sent_time>.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub register_chainpingout($$$$$$$$$) {
+ my ($self, $chaintype, $addr1, $type1, $key1, $addr2, $type2, $key2, $sent_time) = @_;
+
+ my $fh = $self->get_chainping_fh($chaintype, 'out') or
+ Echolot::Log::cluck ("chaintype $chaintype/out has no assigned filehandle."),
+ return undef;
+
+ seek($fh, 0, SEEK_END) or
+ Echolot::Log::warn("Cannot seek to end of chaintype $chaintype out pings: $!."),
+ return undef;
+ print($fh join(' ', $sent_time, $addr1, $type1, $key1, $addr2, $type2, $key2)."\n") or
+ Echolot::Log::warn("Error when writing to chaintype $chaintype out pings: $!."),
+ return undef;
+ $fh->flush();
+ Echolot::Log::debug("registering chainping $chaintype out through $addr1 ($type1; $key1) to $addr2 ($type2; $key2).");
+
+ return 1;
+};
+
+=item $storage->B<register_chainpingdone>( I<$chaintype>, I<$addr1>, I<$type1>, I<$key1>, I<$addr2>, I<$type2>, I<$key2>, I<$sent_time>, I<$latency> )
+
+Register that the chain ping of type I<$chaintype> sent through I<$addr1> (I<$type1>, I<$key1>)
+and I<$addr2> (I<$type2>, I<$key2>) at I$<sent_time>
+has returned with latency I<$latency>.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub register_chainpingdone($$$$$$$$$$) {
+ my ($self, $chaintype, $addr1, $type1, $key1, $addr2, $type2, $key2, $sent_time, $latency) = @_;
+
+ # write ping to done
+ my $fh = $self->get_chainping_fh($chaintype, 'done') or
+ Echolot::Log::cluck ("chaintype $chaintype/done has no assigned filehandle."),
+ return undef;
+ seek($fh, 0, SEEK_END) or
+ Echolot::Log::warn("Cannot seek to end of $chaintype/done pings: $!."),
+ return undef;
+ print($fh join(' ', $sent_time, $addr1, $type1, $key1, $addr2, $type2, $key2, $latency)."\n") or
+ Echolot::Log::warn("Error when writing to $chaintype/done pings: $!."),
+ return undef;
+ $fh->flush();
+ Echolot::Log::debug("registering chainpingdone from ".(scalar localtime $sent_time)." with latency $latency chainping $chaintype out through $addr1 ($type1; $key1) to $addr2 ($type2; $key2).");
+
+ return 1;
+};
+
+=item $storage->B<add_prospective_address>( I<$addr>, I<$reason>, I<$additional> )
+
+Add I<$addr> to the list of prospective remailers with I<$reason> and
+I<$additional> information.
+
+Returns 1.
+
+=cut
+sub add_prospective_address($$$$) {
+ my ($self, $addr, $reason, $additional) = @_;
+
+ return 1 if defined $self->{'METADATA'}->{'addresses'}->{$addr};
+ push @{ $self->{'METADATA'}->{'prospective_addresses'}{$addr} }, time().'; '. $reason. '; '. $additional;
+ $self->commit();
+
+ return 1;
+};
+
+=item $storage->B<commit_prospective_address>( )
+
+Commit prospective remailers to the list of remailers we know.
+
+Returns 1.
+
+=cut
+sub commit_prospective_address($) {
+ my ($self) = @_;
+
+ $self->delay_commit();
+ for my $addr (keys %{$self->{'METADATA'}->{'prospective_addresses'}}) {
+ if (defined $self->{'METADATA'}->{'addresses'}->{$addr}) {
+ delete $self->{'METADATA'}->{'prospective_addresses'}->{$addr};
+ next;
+ };
+
+ # expire old prospective addresses
+ while (@{ $self->{'METADATA'}->{'prospective_addresses'}->{$addr} }) {
+ my ($time, $reason, $additional) = split(/;\s*/, $self->{'METADATA'}->{'prospective_addresses'}->{$addr}->[0] );
+ if ($time < time() - Echolot::Config::get()->{'prospective_addresses_ttl'} ) {
+ shift @{ $self->{'METADATA'}->{'prospective_addresses'}->{$addr} };
+ } else {
+ last;
+ };
+ };
+
+ unless (scalar @{ $self->{'METADATA'}->{'prospective_addresses'}->{$addr} }) {
+ delete $self->{'METADATA'}->{'prospective_addresses'}->{$addr};
+ next;
+ };
+
+ my %reasons;
+ for my $line ( @{ $self->{'METADATA'}->{'prospective_addresses'}->{$addr} } ) {
+ my ($time, $reason, $additional) = split(/;\s*/, $line);
+ push @{ $reasons{$reason} }, $additional;
+ };
+
+ # got prospective by reply to own remailer-conf or remailer-key request
+ if ( defined $reasons{'self-capsstring-conf'} || defined $reasons{'self-capsstring-key'} ) {
+ Echolot::Log::notice("$addr is used because of direct conf or key reply");
+ $self->add_address($addr);
+ delete $self->{'METADATA'}->{'prospective_addresses'}->{$addr};
+ next;
+ }
+
+ # was listed in reliable's remailer-conf reply; @adds holds suggestors
+ my @adds;
+ push @adds, @{ $reasons{'reliable-caps-reply-type1'} } if defined $reasons{'reliable-caps-reply-type1'};
+ push @adds, @{ $reasons{'reliable-caps-reply-type2'} } if defined $reasons{'reliable-caps-reply-type2'};
+ if (scalar @adds) {
+ my %unique;
+ @adds = grep { ! $unique{$_}++; } @adds;
+ if (scalar @adds >= Echolot::Config::get()->{'reliable_auto_add_min'} ) {
+ Echolot::Log::notice("$addr is recommended by ". join(', ', @adds) . ".");
+ $self->add_address($addr);
+ delete $self->{'METADATA'}->{'prospective_addresses'}->{$addr};
+ next;
+ };
+ };
+ };
+
+ $self->enable_commit(1);
+
+ return 1;
+};
+
+=item $storage->B<get_address>( I<$addr> )
+
+Get a reference to a hash of information of the remailers with address
+I<$addr>.
+
+The hash has the following keys:
+
+=over
+
+=item status
+
+=item id
+
+=item address
+
+=item fetch
+
+=item showit
+
+=item pingit
+
+=item ttl
+
+=item resurrection_ttl
+
+=back
+
+Returns undef on errors.
+
+=cut
+sub get_address($$) {
+ my ($self, $addr) = @_;
+
+ defined ($self->{'METADATA'}->{'addresses'}->{$addr}) or
+ Echolot::Log::cluck ("$addr does not exist in Metadata."),
+ return undef;
+
+ my $result = {
+ status => $self->{'METADATA'}->{'addresses'}->{$addr}->{'status'},
+ id => $self->{'METADATA'}->{'addresses'}->{$addr}->{'id'},
+ address => $_,
+ fetch => $self->{'METADATA'}->{'addresses'}->{$addr}->{'fetch'},
+ showit => $self->{'METADATA'}->{'addresses'}->{$addr}->{'showit'},
+ pingit => $self->{'METADATA'}->{'addresses'}->{$addr}->{'pingit'},
+ ttl => $self->{'METADATA'}->{'addresses'}->{$addr}->{'ttl'},
+ resurrection_ttl => $self->{'METADATA'}->{'addresses'}->{$addr}->{'resurrection_ttl'},
+ };
+
+ return $result;
+};
+
+=item $storage->B<get_addresses>( )
+
+Get an array of all remailers we know about. Each element in this array is a
+hash reference as returned by C<get_address>.
+
+=cut
+sub get_addresses($) {
+ my ($self) = @_;
+
+ my @addresses = keys %{$self->{'METADATA'}->{'addresses'}};
+ my @return_data = map { $self->get_address($_); } @addresses;
+ return @return_data;
+};
+
+=item $storage->B<add_address>( I<$addr> )
+
+Adds a remailer with address I<$addr>. B<fetch>, B<pingit>, and B<shoit> are
+set to the values configured for new remailers.
+
+Assign the remailer status B<active> and a new unique ID.
+
+See L<pingd.conf(5)> for more information on this.
+
+Returns 1.
+
+=cut
+sub add_address($$) {
+ my ($self, $addr) = @_;
+
+ my @all_addresses = $self->get_addresses();
+ my $maxid = $self->{'METADATA'}->{'addresses_maxid'};
+ unless (defined $maxid) {
+ $maxid = 0;
+ for my $addr (@all_addresses) {
+ if ($addr->{'id'} > $maxid) {
+ $maxid = $addr->{'id'};
+ };
+ };
+ };
+
+
+
+ Echolot::Log::notice("Adding address $addr.");
+
+ my $remailer = {
+ id => $maxid + 1,
+ status => 'active',
+ ttl => Echolot::Config::get()->{'addresses_default_ttl'},
+ fetch => Echolot::Config::get()->{'fetch_new'},
+ pingit => Echolot::Config::get()->{'ping_new'},
+ showit => Echolot::Config::get()->{'show_new'},
+ };
+ $self->{'METADATA'}->{'addresses'}->{$addr} = $remailer;
+ $self->{'METADATA'}->{'addresses_maxid'} = $maxid+1;
+ $self->commit();
+
+ return 1;
+};
+
+=item $storage->B<set_stuff>( I<@args> )
+
+@args is supposed to have two elements: I<$address>, and I<$setting>.
+
+Set verious options for the remailer with address $I<$address>.
+
+I<$setting> has to be of the form C<key=value>. Recognised keys are B<pingit>,
+B<fetch>, and B<showit>. Acceptable values are B<on> and B<off>.
+
+See L<pingd(1)> for the meaning of these settings.
+
+Returns 1, undef on error.
+
+=cut
+sub set_stuff($@) {
+ my ($self, @args) = @_;
+
+ my ($addr, $setting) = @args;
+ my $args = join(', ', @args);
+
+ defined ($addr) or
+ Echolot::Log::cluck ("Could not get address for '$args'."),
+ return undef;
+ defined ($setting) or
+ Echolot::Log::cluck ("Could not get setting for '$args'."),
+ return undef;
+
+ defined ($self->{'METADATA'}->{'addresses'}->{$addr}) or
+ Echolot::Log::warn ("Address $addr does not exist."),
+ return undef;
+
+
+ if ($setting =~ /^(pingit|fetch|showit)=(on|off)$/) {
+ my $option = $1;
+ my $value = $2;
+ Echolot::Log::info("Setting $option to $value for $addr");
+ $self->{'METADATA'}->{'addresses'}->{$addr}->{$option} = ($value eq 'on');
+ } else {
+ Echolot::Log::warn ("Don't know what to do with '$setting' for $addr."),
+ return undef;
+ }
+
+ $self->commit();
+ return 1;
+};
+
+
+=item $storage->B<get_address_by_id>( I<$id> )
+
+Return the address for the remailer with id I<$id>.
+
+Return undef if there is no remailer with that id.
+
+=cut
+sub get_address_by_id($$) {
+ my ($self, $id) = @_;
+
+ my @addresses = grep {$self->{'METADATA'}->{'addresses'}->{$_}->{'id'} == $id}
+ keys %{$self->{'METADATA'}->{'addresses'}};
+ return undef unless (scalar @addresses);
+ if (scalar @addresses >= 2) {
+ Echolot::Log::cluck("Searching for address by id '$id' gives more than one result.");
+ };
+ my %return_data = %{$self->{'METADATA'}->{'addresses'}->{$addresses[0]}};
+ $return_data{'address'} = $addresses[0];
+ return \%return_data;
+};
+
+=item $storage->B<decrease_resurrection_ttl>( I<$address> )
+
+Decrease the TTL (Time To Live) for remailer with address I<$address> by one.
+
+If it hits zero the remailer's status is set to B<ttl timeout>.
+
+Returns 1, undef on error.
+
+=cut
+sub decrease_ttl($$) {
+ my ($self, $address) = @_;
+
+ defined ($self->{'METADATA'}->{'addresses'}->{$address}) or
+ Echolot::Log::cluck ("$address does not exist in Metadata address list."),
+ return undef;
+ $self->{'METADATA'}->{'addresses'}->{$address}->{'ttl'} --;
+ $self->{'METADATA'}->{'addresses'}->{$address}->{'status'} = 'ttl timeout',
+ Echolot::Log::info("Remailer $address disabled: ttl expired."),
+ $self->{'METADATA'}->{'addresses'}->{$address}->{'resurrection_ttl'} = Echolot::Config::get()->{'check_resurrection_ttl'}
+ if ($self->{'METADATA'}->{'addresses'}->{$address}->{'ttl'} <= 0);
+ $self->commit();
+ return 1;
+};
+
+=item $storage->B<decrease_resurrection_ttl>( I<$address> )
+
+Decrease the resurrection TTL (Time To Live) for remailer with address
+I<$address> by one.
+
+If it hits zero the remailer's status is set to B<dead>.
+
+Returns 1, undef on error.
+
+=cut
+sub decrease_resurrection_ttl($$) {
+ my ($self, $address) = @_;
+
+ defined ($self->{'METADATA'}->{'addresses'}->{$address}) or
+ Echolot::Log::cluck ("$address does not exist in Metadata address list."),
+ return 0;
+ ($self->{'METADATA'}->{'addresses'}->{$address}->{'status'} eq 'ttl timeout') or
+ Echolot::Log::cluck ("$address is not in ttl timeout status."),
+ return 0;
+ $self->{'METADATA'}->{'addresses'}->{$address}->{'resurrection_ttl'} --;
+ $self->{'METADATA'}->{'addresses'}->{$address}->{'status'} = 'dead',
+ Echolot::Log::info("Remailer $address is dead."),
+ if ($self->{'METADATA'}->{'addresses'}->{$address}->{'resurrection_ttl'} <= 0);
+ $self->commit();
+ return 1;
+};
+
+=item $storage->B<restore_ttl>( I<$address> )
+
+Restore the TTL (Time To Live) for remailer with address I<$address> to the
+value configured with I<addresses_default_ttl>
+
+See L<pingd.conf(5)> for more information on this settings.
+
+Returns 1, undef on error.
+
+=cut
+sub restore_ttl($$) {
+ my ($self, $address) = @_;
+
+ defined ($self->{'METADATA'}->{'addresses'}->{$address}) or
+ Echolot::Log::cluck ("$address does not exist in Metadata address list."),
+ return undef;
+ defined ($self->{'METADATA'}->{'addresses'}->{$address}->{'status'}) or
+ Echolot::Log::cluck ("$address does exist in Metadata address list but does not have status defined."),
+ return undef;
+ Echolot::Log::info("Remailer $address is alive and active again.")
+ unless ($self->{'METADATA'}->{'addresses'}->{$address}->{'status'} eq 'active');
+ $self->{'METADATA'}->{'addresses'}->{$address}->{'ttl'} = Echolot::Config::get()->{'addresses_default_ttl'};
+ delete $self->{'METADATA'}->{'addresses'}->{$address}->{'resurrection_ttl'};
+ $self->{'METADATA'}->{'addresses'}->{$address}->{'status'} = 'active' if
+ ($self->{'METADATA'}->{'addresses'}->{$address}->{'status'} eq 'ttl timeout' ||
+ $self->{'METADATA'}->{'addresses'}->{$address}->{'status'} eq 'dead');
+ $self->commit();
+ return 1;
+};
+
+
+=item $storage->B<not_a_remaielr>( I<$id> )
+
+Set the remailer whoise id is I<$id> to B<disabled by user reply: is not a
+remailer>.
+
+Returns 1, undef on error.
+
+=cut
+sub not_a_remailer($$) {
+ my ($self, $id) = @_;
+
+ my $remailer = $self->get_address_by_id($id);
+ defined $remailer or
+ Echolot::Log::cluck("No remailer found for id '$id'."),
+ return undef;
+ my $address = $remailer->{'address'};
+ defined ($self->{'METADATA'}->{'addresses'}->{$address}) or
+ Echolot::Log::cluck ("$address does not exist in Metadata address list."),
+ return undef;
+ $self->{'METADATA'}->{'addresses'}->{$address}->{'status'} = 'disabled by user reply: is not a remailer';
+
+ Echolot::Log::info("Setting $id, $address to disabled by user reply.");
+
+ $self->commit();
+ return 1;
+};
+
+=item $storage->B<set_caps>( I<$type>, I<$caps>, I<$nick>, I<$address>, I<$timestamp> [, I<$dont_expire> ])
+
+Sets the capabilities for remailer with address I<$address> to the given
+information (I<$nick>, I<$type>, I<$caps>, I<$timestamp>).
+
+Type here means the software used (Mixmaster, Reliable) as given by the
+remailer-conf reply or something like B<set manually>.
+
+If there already is newer information about that key than I<$timestamp> the
+update is disregarded.
+
+If I<$dont_expire> is defined the setting is copied to the remailers metadata
+as well.
+
+Returns 1.
+
+=cut
+sub set_caps($$$$$$;$) {
+ my ($self, $type, $caps, $nick, $address, $timestamp, $dont_expire) = @_;
+
+ (defined $address) or
+ Echolot::Log::cluck ("$address not defined in set_key.");
+
+ if (! defined $self->{'metadata'}->{'remailers'}->{$address} ) {
+ $self->{'metadata'}->{'remailers'}->{$address} = {};
+ };
+
+ if (! defined $self->{'METADATA'}->{'remailers'}->{$address}->{'conf'}) {
+ $self->{'METADATA'}->{'remailers'}->{$address}->{'conf'} =
+ {
+ nick => $nick,
+ type => $type,
+ capabilities => $caps,
+ last_update => $timestamp
+ };
+ } else {
+ my $conf = $self->{'METADATA'}->{'remailers'}->{$address}->{'conf'};
+ if ($conf->{'last_update'} >= $timestamp) {
+ Echolot::Log::info("Stored data is already newer for remailer $nick.");
+ return 1;
+ };
+ $conf->{'last_update'} = $timestamp;
+ if ($conf->{'nick'} ne $nick) {
+ Echolot::Log::info($conf->{'nick'}." was renamed to $nick.");
+ $conf->{'nick'} = $nick;
+ };
+ if ($conf->{'capabilities'} ne $caps) {
+ Echolot::Log::info("$nick has a new caps string '$caps' old: '".$conf->{'capabilities'}."'.");
+ $conf->{'capabilities'} = $caps;
+ };
+ if ($conf->{'type'} ne $type) {
+ Echolot::Log::info("$nick has a new type string '$type'.");
+ $conf->{'type'} = $type;
+ };
+ };
+
+ if (defined $dont_expire) {
+ $self->{'METADATA'}->{'remailers'}->{$address}->{'conf'}->{'dont_expire'} = $dont_expire;
+ };
+
+ $self->commit();
+
+ return 1;
+};
+
+=item $storage->B<set_key>( I<$type>, I<$nick>, I<$address>, I<$key>, I<$keyid>, I<$version>, I<$caps>, I<$summary>, I<$timestamp>)
+
+Sets the I<$type> key I<$keyid> for remailer with address I<$address> to the
+given information (I<$nick>, I<$key>, I<$caps>, I<$summary>, I<$timestamp>).
+
+If there already is newer information about that key than I<$timestamp> the
+update is disregarded.
+
+Returns 1.
+
+=cut
+sub set_key($$$$$$$$$) {
+ my ($self, $type, $nick, $address, $key, $keyid, $version, $caps, $summary, $timestamp) = @_;
+
+ (defined $address) or
+ Echolot::Log::cluck ("$address not defined in set_key.");
+
+ if (! defined $self->{'metadata'}->{'remailers'}->{$address} ) {
+ $self->{'metadata'}->{'remailers'}->{$address} = {};
+ };
+
+ if (! defined $self->{'METADATA'}->{'remailers'}->{$address}->{'keys'}) {
+ $self->{'METADATA'}->{'remailers'}->{$address}->{'keys'} = {};
+ };
+ if (! defined $self->{'METADATA'}->{'remailers'}->{$address}->{'keys'}->{$type}) {
+ $self->{'METADATA'}->{'remailers'}->{$address}->{'keys'}->{$type} = {};
+ };
+
+ if (! defined $self->{'METADATA'}->{'remailers'}->{$address}->{'keys'}->{$type}->{$keyid}) {
+ $self->{'METADATA'}->{'remailers'}->{$address}->{'keys'}->{$type}->{$keyid} =
+ {
+ key => $key,
+ summary => $summary,
+ nick => $nick,
+ last_update => $timestamp
+ };
+ } else {
+ my $keyref = $self->{'METADATA'}->{'remailers'}->{$address}->{'keys'}->{$type}->{$keyid};
+ if ($keyref->{'last_update'} >= $timestamp) {
+ Echolot::Log::info("Stored data is already newer for remailer $nick.");
+ return 1;
+ };
+ $keyref->{'last_update'} = $timestamp;
+ if ($keyref->{'nick'} ne $nick) {
+ Echolot::Log::info("$nick has a new key nick string '$nick' old: '".$keyref->{'nick'}."'.");
+ $keyref->{'nick'} = $nick;
+ };
+ if ($keyref->{'summary'} ne $summary) {
+ Echolot::Log::info("$nick has a new key summary string '$summary' old: '".$keyref->{'summary'}."'.");
+ $keyref->{'summary'} = $summary;
+ };
+ if ($keyref->{'key'} ne $key) {
+ #Echolot::Log::info("$nick has a new key string '$key' old: '".$keyref->{'key'}."' - This probably should not happen.");
+ Echolot::Log::info("$nick has a new key string for same keyid $keyid.");
+ $keyref->{'key'} = $key;
+ };
+ };
+ $self->commit();
+
+ return 1;
+};
+
+=item $storage->B<get_secret>( )
+
+Return our secret (Used in Message Authentication Codes).
+
+=cut
+sub get_secret($) {
+ my ($self) = @_;
+
+ return $self->{'METADATA'}->{'secret'};
+};
+
+=item $storage->B<get_types>( I<$remailer> )
+
+Get an array of types supported by remailer with address I<$remailer>.
+
+Returns undef on errors.
+
+¿ It may be possible that a type is returned but then has no keys. This may be
+a bug, I'm not sure.
+
+=cut
+sub get_types($$) {
+ my ($self, $remailer) = @_;
+
+ defined ($self->{'METADATA'}->{'addresses'}->{$remailer}) or
+ Echolot::Log::cluck ("$remailer does not exist in Metadata remailer list."),
+ return undef;
+
+ return () unless defined $self->{'METADATA'}->{'remailers'}->{$remailer};
+ return () unless defined $self->{'METADATA'}->{'remailers'}->{$remailer}->{'keys'};
+ my @types = keys %{$self->{'METADATA'}->{'remailers'}->{$remailer}->{'keys'}};
+ return @types;
+};
+
+
+=item $storage->B<has_type>( I<$remailer>, I<$type> )
+
+Checks if the remailer with address I<$remailer> has type I<$type> keys.
+
+Returns 1 if it has, 0 if not, undef on errors.
+
+=cut
+sub has_type($$$) {
+ my ($self, $remailer, $type) = @_;
+
+ defined ($self->{'METADATA'}->{'addresses'}->{$remailer}) or
+ Echolot::Log::cluck ("$remailer does not exist in Metadata remailer list."),
+ return undef;
+
+ return 0 unless defined $self->{'METADATA'}->{'remailers'}->{$remailer};
+ return 0 unless defined $self->{'METADATA'}->{'remailers'}->{$remailer}->{'keys'};
+ return 0 unless defined $self->{'METADATA'}->{'remailers'}->{$remailer}->{'keys'}->{$type};
+ return 0 unless scalar keys %{$self->{'METADATA'}->{'remailers'}->{$remailer}->{'keys'}->{$type}};
+ return 1;
+};
+
+
+=item $storage->B<get_keys>( I<$remailer>, I<$type> )
+
+Returns an array listing all keyids of type I<$type> of remailer with address
+I<$remailer>.
+
+Returns undef on errors.
+
+=cut
+sub get_keys($$$) {
+ my ($self, $remailer, $type) = @_;
+
+ defined ($self->{'METADATA'}->{'addresses'}->{$remailer}) or
+ Echolot::Log::cluck ("$remailer does not exist in Metadata address list."),
+ return undef;
+
+ defined ($self->{'METADATA'}->{'remailers'}->{$remailer}) or
+ Echolot::Log::cluck ("$remailer does not exist in Metadata remailer list."),
+ return undef;
+
+ defined ($self->{'METADATA'}->{'remailers'}->{$remailer}->{'keys'}) or
+ Echolot::Log::cluck ("$remailer does not have keys in Metadata remailer list."),
+ return undef;
+
+ defined ($self->{'METADATA'}->{'remailers'}->{$remailer}->{'keys'}->{$type}) or
+ Echolot::Log::cluck ("$remailer does not have type '$type' in Metadata remailer list."),
+ return undef;
+
+ my @keys = keys %{$self->{'METADATA'}->{'remailers'}->{$remailer}->{'keys'}->{$type}};
+ return @keys;
+};
+
+
+
+=item $storage->B<get_key>( I<$remailer>, I<$type>, I<$key> )
+
+Returns a hash having they keys C<summary>, C<key>, C<nick>, and
+C<last_updated> of the I<$type> key with id I<$key> of remailer with address
+I<$remailer>.
+
+Returns undef on errors.
+
+=cut
+sub get_key($$$$) {
+ my ($self, $remailer, $type, $key) = @_;
+
+ defined ($self->{'METADATA'}->{'addresses'}->{$remailer}) or
+ Echolot::Log::cluck ("$remailer does not exist in Metadata address list."),
+ return undef;
+
+ defined ($self->{'METADATA'}->{'remailers'}->{$remailer}) or
+ Echolot::Log::cluck ("$remailer does not exist in Metadata remailer list."),
+ return undef;
+
+ defined ($self->{'METADATA'}->{'remailers'}->{$remailer}->{'keys'}) or
+ Echolot::Log::cluck ("$remailer does not have keys in Metadata remailer list."),
+ return undef;
+
+ defined ($self->{'METADATA'}->{'remailers'}->{$remailer}->{'keys'}->{$type}) or
+ Echolot::Log::cluck ("$remailer does not have type '$type' in Metadata remailer list."),
+ return undef;
+
+ defined ($self->{'METADATA'}->{'remailers'}->{$remailer}->{'keys'}->{$type}->{$key}) or
+ Echolot::Log::cluck ("$remailer does not have key '$key' in type '$type' in Metadata remailer list."),
+ return undef;
+
+ my %result = (
+ summary => $self->{'METADATA'}->{'remailers'}->{$remailer}->{'keys'}->{$type}->{$key}->{'summary'},
+ key => $self->{'METADATA'}->{'remailers'}->{$remailer}->{'keys'}->{$type}->{$key}->{'key'},
+ nick => $self->{'METADATA'}->{'remailers'}->{$remailer}->{'keys'}->{$type}->{$key}->{'nick'},
+ last_update => $self->{'METADATA'}->{'remailers'}->{$remailer}->{'keys'}->{$type}->{$key}->{'last_update'}
+ );
+
+ return %result;
+};
+
+
+=item $storage->B<get_capabilities>( I<$remailer> )
+
+Return the capabilities on file for remailer with address I<$remailer>. This
+is probably the one we got from remailer-conf or set manually.
+
+Returns undef on errors.
+
+=cut
+sub get_capabilities($$) {
+ my ($self, $remailer) = @_;
+
+ return undef unless defined $self->{'METADATA'}->{'remailers'}->{$remailer};
+ return undef unless defined $self->{'METADATA'}->{'remailers'}->{$remailer}->{'conf'};
+ return $self->{'METADATA'}->{'remailers'}->{$remailer}->{'conf'}->{'capabilities'};
+};
+
+
+=item $storage->B<get_capabilities>( I<$remailer> )
+
+Return the capabilities on file for remailer with address I<$remailer>. This
+is probably the one we got from remailer-conf or set manually.
+
+Returns undef on errors.
+
+=cut
+sub get_nick($$) {
+ my ($self, $remailer) = @_;
+
+ defined $remailer or
+ Echolot::Log::cluck ("Undefined remailer passed to get_nick()."),
+ return undef;
+ return undef unless defined $self->{'METADATA'}->{'remailers'}->{$remailer};
+ return undef unless defined $self->{'METADATA'}->{'remailers'}->{$remailer}->{'conf'};
+ return $self->{'METADATA'}->{'remailers'}->{$remailer}->{'conf'}->{'nick'};
+};
+
+
+=item $storage->B<expire>( )
+
+Expires old keys, confs and pings from the Storage as configured by
+I<expire_keys>, I<expire_confs>, and I<expire_pings>.
+
+See L<pingd.conf(5)> for more information on these settings.
+
+Returns 1 on success, undef on errors.
+
+=cut
+sub expire($) {
+ my ($self) = @_;
+
+ my $now = time();
+ my $expire_keys = $now - Echolot::Config::get()->{'expire_keys'};
+ my $expire_conf = $now - Echolot::Config::get()->{'expire_confs'};
+ my $expire_pings = $now - Echolot::Config::get()->{'expire_pings'};
+ my $expire_chainpings = $now - Echolot::Config::get()->{'expire_chainpings'};
+ my $expire_fromlines = $now - Echolot::Config::get()->{'expire_fromlines'};
+
+ # Remailer Information and pings
+ for my $remailer_addr ( keys %{$self->{'METADATA'}->{'remailers'}} ) {
+ if (exists $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}) {
+ for my $type ( keys %{$self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}} ) {
+ if (exists $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type}) {
+ for my $key ( keys %{$self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type}} ) {
+ if ($self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type}->{$key}->{'last_update'} < $expire_keys) {
+ Echolot::Log::info("Expiring $remailer_addr, key, $type, $key.");
+ $self->pingdata_close_one($remailer_addr, $type, $key, 'delete');
+ delete $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type}->{$key};
+ };
+ };
+ delete $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type}
+ unless (scalar keys %{$self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type}});
+ };
+ };
+ delete $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}
+ unless (scalar keys %{$self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}});
+ }
+
+ if (exists $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'conf'}) {
+ delete $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'conf'}
+ if (defined $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'conf'} &&
+ ($self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'conf'}->{'last_update'} < $expire_conf) &&
+ ! ($self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'conf'}->{'dont_expire'}));
+ }
+
+ delete $self->{'METADATA'}->{'remailers'}->{$remailer_addr},
+ next
+ unless ( defined ($self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'conf'}) ||
+ defined ($self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}));
+
+
+ next unless exists $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'};
+ for my $type ( keys %{$self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}} ) {
+ next unless exists $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type};
+ for my $key ( keys %{$self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type}} ) {
+ my @out = grep {$_ > $expire_pings} $self->get_pings($remailer_addr, $type, $key, 'out');
+ my @done = grep {$_->[0] > $expire_pings} $self->get_pings($remailer_addr, $type, $key, 'done');
+
+
+ # write ping to done
+ my $fh = $self->get_ping_fh($remailer_addr, $type, $key, 'done') or
+ Echolot::Log::cluck ("$remailer_addr; type=$type; key=$key has no assigned filehandle for done pings."),
+ return undef;
+ seek($fh, 0, SEEK_SET) or
+ Echolot::Log::warn("Cannot seek to start of $remailer_addr out pings: $!."),
+ return undef;
+ truncate($fh, 0) or
+ Echolot::Log::warn("Cannot truncate done pings file for remailer $remailer_addr; key=$key file to zero length: $!."),
+ return undef;
+ for my $done (@done) {
+ print($fh $done->[0]." ".$done->[1]."\n") or
+ Echolot::Log::warn("Error when writing to $remailer_addr out pings: $!."),
+ return undef;
+ };
+ $fh->flush();
+
+ # rewrite outstanding pings
+ $fh = $self->get_ping_fh($remailer_addr, $type, $key, 'out') or
+ Echolot::Log::cluck ("$remailer_addr; type=$type; key=$key has no assigned filehandle for out pings."),
+ return undef;
+ seek($fh, 0, SEEK_SET) or
+ Echolot::Log::warn("Cannot seek to start of outgoing pings file for remailer $remailer_addr; key=$key: $!."),
+ return undef;
+ truncate($fh, 0) or
+ Echolot::Log::warn("Cannot truncate outgoing pings file for remailer $remailer_addr; key=$key file to zero length: $!."),
+ return undef;
+ print($fh (join "\n", @out), (scalar @out ? "\n" : '') ) or
+ Echolot::Log::warn("Error when writing to outgoing pings file for remailer $remailer_addr; key=$key file: $!."),
+ return undef;
+ $fh->flush();
+ };
+ };
+ };
+
+ # Chainpings
+ for my $type ( keys %{$self->{'CHAINPING_FHS'}} ) {
+ my $pings = $self->get_chainpings($type);
+
+ @{ $pings->{'out'} } = map {
+ my $a = $_;
+ join (' ', map ({ $a->{$_} } qw{sent addr1 type1 key1 addr2 type2 key2}))
+ } grep {
+ $_->{'sent'} > $expire_chainpings
+ }
+ @{ $pings->{'out'} };
+ @{ $pings->{'done'} } = map {
+ my $a = $_;
+ join (' ', map ({ $a->{$_} } qw{sent addr1 type1 key1 addr2 type2 key2 lat}))
+ } grep {
+ $_->{'sent'} > $expire_chainpings
+ }
+ @{ $pings->{'done'} };
+
+ for my $dir (qw{out done}) {
+ my $fh = $self->get_chainping_fh($type, $dir) or
+ Echolot::Log::warn ("have no assigned filehandle for $type $dir chainpings."),
+ return undef;
+ seek($fh, 0, SEEK_SET) or
+ Echolot::Log::warn("Cannot seek to start of $dir chainpings $type $!."),
+ return undef;
+ truncate($fh, 0) or
+ Echolot::Log::warn("Cannot truncate $dir chainpings $type file to zero length: $!."),
+ return undef;
+ print($fh (join "\n", @{$pings->{$dir}}), (scalar @{$pings->{$dir}} ? "\n" : '') ) or
+ Echolot::Log::warn("Error when writing to $dir chainpings $type file: $!."),
+ return undef;
+ $fh->flush();
+ };
+ };
+
+ # From Header lines
+ for my $remailer_addr ( keys %{$self->{'METADATA'}->{'fromlines'}} ) {
+ for my $type ( keys %{$self->{'METADATA'}->{'fromlines'}->{$remailer_addr}} ) {
+ for my $user_supplied ( keys %{$self->{'METADATA'}->{'fromlines'}->{$remailer_addr}->{$type}} ) {
+ delete $self->{'METADATA'}->{'fromlines'}->{$remailer_addr}->{$type}->{$user_supplied}
+ if ($self->{'METADATA'}->{'fromlines'}->{$remailer_addr}->{$type}->{$user_supplied}->{'last_update'} < $expire_fromlines);
+ };
+ delete $self->{'METADATA'}->{'fromlines'}->{$remailer_addr}->{$type}
+ unless (scalar keys %{$self->{'METADATA'}->{'fromlines'}->{$remailer_addr}->{$type}});
+ };
+ delete $self->{'METADATA'}->{'fromlines'}->{$remailer_addr}
+ unless (scalar keys %{$self->{'METADATA'}->{'fromlines'}->{$remailer_addr}});
+ };
+
+ $self->commit();
+
+ return 1;
+};
+
+=item $storage->B<delete_remailer>( I<$address> )
+
+Delete all data on the remailer with I<$address>. This includes stored conf
+and key information, pings and the remailer's settings like I<pingit> et al.
+
+If this remailer is still referenced by other remailers' remailer-conf reply it
+is likely to get picked up again.
+
+Returns 1.
+
+=cut
+sub delete_remailer($$) {
+ my ($self, $address) = @_;
+
+ Echolot::Log::notice("Deleting remailer $address.");
+
+ if (defined $self->{'METADATA'}->{'addresses'}->{$address}) {
+ delete $self->{'METADATA'}->{'addresses'}->{$address}
+ } else {
+ Echolot::Log::cluck("Remailer $address does not exist in addresses.")
+ };
+
+ if (defined $self->{'METADATA'}->{'remailers'}->{$address}) {
+ for my $type ( keys %{$self->{'METADATA'}->{'remailers'}->{$address}->{'keys'}} ) {
+ for my $key ( keys %{$self->{'METADATA'}->{'remailers'}->{$address}->{'keys'}->{$type}} ) {
+ $self->pingdata_close_one($address, $type, $key, 'delete');
+ };
+ };
+
+ delete $self->{'METADATA'}->{'remailers'}->{$address}
+ };
+
+ delete $self->{'METADATA'}->{'fromlines'}->{$address}
+ if (defined $self->{'METADATA'}->{'fromlines'}->{$address});
+
+ $self->commit();
+
+ return 1;
+};
+
+=item $storage->B<delete_remailercaps>( I<$address> )
+
+Delete conf data of the remailer with I<$address>.
+
+Returns 1.
+
+=cut
+sub delete_remailercaps($$) {
+ my ($self, $address) = @_;
+
+ Echolot::Log::info("Deleting conf for remailer $address.");
+
+ if (defined $self->{'METADATA'}->{'remailers'}->{$address}) {
+ delete $self->{'METADATA'}->{'remailers'}->{$address}->{'conf'}
+ if defined $self->{'METADATA'}->{'remailers'}->{$address}->{'conf'};
+ } else {
+ Echolot::Log::cluck("Remailer $address does not exist in remailers.")
+ };
+ $self->commit();
+
+ return 1;
+};
+
+
+=item $storage->B<register_fromline>( I<$address>, I<$with_from>, I<$from>, $I<disclaimer_top>, $I<disclaimer_bot> )
+
+Register that the remailer I<$address> returned the From header
+line I<$from>. If I<$with_from> is 1 we had tried to supply our own
+From, otherwise not.
+
+$I<disclaimer_top> and $I<disclaimer_bot> are boolean variables indicating
+presence or absense of any disclaimer.
+
+Returns 1, undef on error.
+
+=cut
+
+sub register_fromline($$$$$$$) {
+ my ($self, $address, $type, $with_from, $from, $top, $bot) = @_;
+
+ defined ($self->{'METADATA'}->{'addresses'}->{$address}) or
+ Echolot::Log::cluck ("$address does not exist in Metadata address list."),
+ return undef;
+ defined ($from) or
+ Echolot::Log::cluck ("from is not defined in register_fromline."),
+ return undef;
+ defined ($with_from) or
+ Echolot::Log::cluck ("from is not defined in register_fromline."),
+ return undef;
+ ($with_from == 0 || $with_from == 1) or
+ Echolot::Log::cluck ("with_from has evil value $with_from in register_fromline."),
+ return undef;
+
+ Echolot::Log::debug("registering fromline $address, $type, $with_from, $from, $top, $bot.");
+
+ $self->{'METADATA'}->{'fromlines'}->{$address}->{$type}->{$with_from} = {
+ last_update => time(),
+ from => $from,
+ disclaim_top => $top,
+ disclaim_bot => $bot,
+ };
+ $self->commit();
+
+ return 1;
+};
+
+
+=item $storage->B<get_fromline>( I<$addr>, I<$type>, I<$user_supplied> )
+
+Return a hash reference with header From line information.
+
+The hash has two keys, B<last_update> and B<from>, which holds the actual information.
+
+If there is no from line registered for the given combination, undef is returned.
+
+On Error, also undef is returned.
+
+=cut
+
+sub get_fromline($$$$) {
+ my ($self, $addr, $type, $user_supplied) = @_;
+
+ defined $self->{'METADATA'}->{'fromlines'}->{$addr} or
+ return undef;
+ defined $self->{'METADATA'}->{'fromlines'}->{$addr}->{$type} or
+ return undef;
+ defined $self->{'METADATA'}->{'fromlines'}->{$addr}->{$type}->{$user_supplied} or
+ return undef;
+
+ defined $self->{'METADATA'}->{'fromlines'}->{$addr}->{$type}->{$user_supplied}->{'last_update'} or
+ Echolot::Log::cluck ("last_update is undefined with $addr $type $user_supplied."),
+ return undef;
+ defined $self->{'METADATA'}->{'fromlines'}->{$addr}->{$type}->{$user_supplied}->{'from'} or
+ Echolot::Log::cluck ("from is undefined with $addr $type $user_supplied."),
+ return undef;
+
+ return { last_update => $self->{'METADATA'}->{'fromlines'}->{$addr}->{$type}->{$user_supplied}->{'last_update'},
+ from => $self->{'METADATA'}->{'fromlines'}->{$addr}->{$type}->{$user_supplied}->{'from'},
+ disclaim_top => $self->{'METADATA'}->{'fromlines'}->{$addr}->{$type}->{$user_supplied}->{'disclaim_top'},
+ disclaim_bot => $self->{'METADATA'}->{'fromlines'}->{$addr}->{$type}->{$user_supplied}->{'disclaim_bot'} };
+}
+
+
+# sub convert($) {
+# my ($self) = @_;
+#
+# for my $remailer_addr ( keys %{$self->{'METADATA'}->{'remailers'}} ) {
+# for my $type ( keys %{$self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}} ) {
+# for my $key ( keys %{$self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type}} ) {
+# if (defined $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'stats'}->{$type}->{$key}) {
+# $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'keys'}->{$type}->{$key}->{'stats'} =
+# $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'stats'}->{$type}->{$key};
+# };
+# };
+# };
+# delete $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'stats'};
+# };
+#
+# $self->commit();
+# };
+#
+# sub convert($) {
+# my ($self) = @_;
+#
+# for my $remailer_addr ( keys %{$self->{'METADATA'}->{'addresses'}} ) {
+# $self->{'METADATA'}->{'addresses'}->{$remailer_addr}->{'fetch'} = 1;
+# $self->{'METADATA'}->{'addresses'}->{$remailer_addr}->{'pingit'} = 1;
+# $self->{'METADATA'}->{'addresses'}->{$remailer_addr}->{'showit'} = 0;
+# delete $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'pingit'};
+# delete $self->{'METADATA'}->{'remailers'}->{$remailer_addr}->{'showit'};
+# };
+#
+# $self->commit();
+# };
+
+=back
+
+=cut
+
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Thesaurus.pm b/trunk/Echolot/Thesaurus.pm
new file mode 100644
index 0000000..0ff207a
--- /dev/null
+++ b/trunk/Echolot/Thesaurus.pm
@@ -0,0 +1,144 @@
+package Echolot::Thesaurus;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Thesaurus - build thesaurus pages
+
+=head1 DESCRIPTION
+
+This package provides necessary functions for the thesaurus.
+
+=cut
+
+use strict;
+use English;
+use Echolot::Log;
+
+
+sub save_thesaurus($$$) {
+ my ($otype, $oid, $data) = @_;
+
+ return 1 unless Echolot::Config::get()->{'thesaurus'};
+
+ my ($type) = $otype =~ /^([a-z-]+)$/;
+ Echolot::Log::cluck("type '$otype' is not clean in save_thesaurus."), return 0 unless defined $type;
+ my ($id) = $oid =~ /^([0-9]+)$/;
+ Echolot::Log::cluck("id '$oid' is not clean in save_thesaurus."), return 0 unless defined $id;
+
+ my $file = Echolot::Config::get()->{'thesaurusdir'}.'/'.$id.'.'.$type;
+ open (F, ">$file") or
+ Echolot::Log::warn ("Cannot open '$file': $!."),
+ return 0;
+ print F $data;
+ close (F);
+
+ return 1;
+};
+
+sub build_thesaurus() {
+ return 1 unless Echolot::Config::get()->{'thesaurus'};
+
+ my $dir = Echolot::Config::get()->{'thesaurusdir'};
+ opendir(DIR, $dir) or
+ Echolot::Log::warn ("Cannot open '$dir': $!."),
+ return 0;
+ my @files = grep { ! /^\./ } readdir(DIR);
+ closedir(DIR);
+
+
+ my $expire_date = time() - Echolot::Config::get()->{'expire_thesaurus'};
+
+ my $data;
+ for my $filename (@files) {
+ my ($id, $what) = $filename =~ /^(\d+)\.(adminkey|conf|help|key|stats)$/;
+ next unless (defined $id && defined $what);
+
+ my $remailer = Echolot::Globals::get()->{'storage'}->get_address_by_id($id);
+ next unless defined $remailer;
+ next unless $remailer->{'showit'};
+ my $caps = Echolot::Globals::get()->{'storage'}->get_capabilities($remailer->{'address'});
+ next unless defined $caps;
+ next unless $caps !~ m/\btesting\b/i;
+
+ my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
+ $atime,$mtime,$ctime,$blksize,$blocks)
+ = stat($dir.'/'.$filename);
+
+ if ($mtime < $expire_date) {
+ unlink ($dir.'/'.$filename) or
+ Echolot::Log::warn("Cannot unlink expired $filename.");
+ Echolot::Log::info("Expired thesaurus file $filename.");
+ next;
+ };
+
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday)
+ = gmtime($mtime);
+
+ my $date = sprintf("%04d-%02d-%02d", $year+1900, $mon+1, $mday);
+ my $time = sprintf("%02d:%02d", $hour, $min);
+
+
+ $data->{$remailer->{'address'}}->{$what.'_href'} = $filename;
+ $data->{$remailer->{'address'}}->{$what.'_date'} = $date;
+ $data->{$remailer->{'address'}}->{$what.'_time'} = $time;
+ $data->{$remailer->{'address'}}->{'id'} = $id;
+ };
+
+
+ for my $addr (keys (%$data)) {
+ my $nick = Echolot::Globals::get()->{'storage'}->get_nick($addr);
+ if (defined $nick) {
+ $data->{$addr}->{'nick'} = $nick;
+ $data->{$addr}->{'address'} = $addr;
+ } else {
+ delete $data->{$addr};
+ };
+ };
+
+ my @data = map {$data->{$_}} (sort { $data->{$a}->{'nick'} cmp $data->{$b}->{'nick'} } keys (%$data));
+
+
+ Echolot::Tools::write_HTML_file(
+ Echolot::Config::get()->{'thesaurusindexfile'},
+ 'thesaurusindexfile',
+ Echolot::Config::get()->{'buildthesaurus'},
+ remailers => \@data);
+
+ open(F, ">$dir/index.txt") or
+ Echolot::Log::warn ("Cannot open '$dir/index.txt': $!."),
+ return 0;
+ for my $remailer (@data) {
+ printf F "%s\t%s\t%s\n", $remailer->{'nick'}, $remailer->{'id'}, $remailer->{'address'};
+ };
+ close(F) or
+ Echolot::Log::warn ("Cannot close '$dir/index.txt': $!."),
+ return 0;
+};
+
+
+1;
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/Echolot/Tools.pm b/trunk/Echolot/Tools.pm
new file mode 100644
index 0000000..59d887f
--- /dev/null
+++ b/trunk/Echolot/Tools.pm
@@ -0,0 +1,476 @@
+package Echolot::Tools;
+
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+=pod
+
+=head1 Name
+
+Echolot::Tools - Tools for echolot
+
+=head1 DESCRIPTION
+
+
+=cut
+
+use strict;
+use HTML::Template;
+use Digest::MD5 qw{};
+use IO::Select;
+use IO::Handle;
+use GnuPG::Interface;
+use Echolot::Log;
+use English;
+
+sub hash($) {
+ my ($data) = @_;
+ ($data) = $data =~ m/(.*)/s; # untaint
+ my $hash = Digest::MD5::md5_hex($data);
+ return $hash;
+};
+
+sub make_random($;%) {
+ my ($length, %args) = @_;
+
+ my $random;
+
+ open (FH, Echolot::Config::get()->{'dev_random'}) or
+ Echolot::Log::warn("Cannot open ".Echolot::Config::get()->{'dev_random'}." for reading: $!."),
+ return 0;
+ read(FH, $random, $length) or
+ Echolot::Log::warn("Cannot read from ".Echolot::Config::get()->{'dev_random'}.": $!."),
+ return 0;
+ close (FH) or
+ Echolot::Log::warn("Cannot close ".Echolot::Config::get()->{'dev_random'}.": $!."),
+ return 0;
+
+ $random = unpack('H*', $random)
+ if ($args{'armor'} == 1);
+
+ return $random;
+};
+
+sub make_mac($) {
+ my ($token) = @_;
+
+ my $mac = hash($token . Echolot::Globals::get()->{'storage'}->get_secret() );
+ return $mac;
+};
+
+sub makeShortNumHash($) {
+ my ($text) = @_;
+
+ my $hash = Echolot::Tools::make_mac($text);
+ $hash = substr($hash, 0, 4);
+ my $sum = hex($hash);
+ return $sum;
+};
+
+sub verify_mac($$) {
+ my ($token, $mac) = @_;
+
+ return (hash($token . Echolot::Globals::get()->{'storage'}->get_secret() ) eq $mac);
+};
+
+sub make_address($) {
+ my ($subsystem) = @_;
+
+ my $token = $subsystem.'='.time();
+ my $hash = hash($token . Echolot::Globals::get()->{'storage'}->get_secret() );
+ my $cut_hash = substr($hash, 0, Echolot::Config::get()->{'hash_len'});
+ my $complete_token = $token.'='.$cut_hash;
+ my $address = Echolot::Config::get()->{'recipient_delimiter'} ne ''?
+ Echolot::Config::get()->{'my_localpart'}.
+ Echolot::Config::get()->{'recipient_delimiter'}.
+ $complete_token.
+ '@'.
+ Echolot::Config::get()->{'my_domain'}
+ :
+ Echolot::Config::get()->{'my_localpart'}.
+ '@'.
+ Echolot::Config::get()->{'my_domain'}.
+ '('.
+ $complete_token.
+ ')';
+
+ return $address;
+};
+
+sub verify_address_tokens($) {
+ my ($address) = @_;
+
+ my ($type, $timestamp, $received_hash);
+ if (Echolot::Config::get()->{'recipient_delimiter'} ne '') {
+ my $delimiter = quotemeta( Echolot::Config::get()->{'recipient_delimiter'});
+ ($type, $timestamp, $received_hash) = $address =~ /$delimiter (.*) = (\d+) = ([0-9a-f]+) @/x or
+ ($type, $timestamp, $received_hash) = $address =~ /\( (.*) = (\d+) = ([0-9a-f]+) \)/x or
+ Echolot::Log::debug("Could not parse to header '$address'."),
+ return undef;
+ } else {
+ ($type, $timestamp, $received_hash) = $address =~ /\( (.*) = (\d+) = ([0-9a-f]+) \)/x or
+ Echolot::Log::debug("Could not parse to header '$address'."),
+ return undef;
+ };
+
+ my $token = $type.'='.$timestamp;
+ my $hash = Echolot::Tools::hash($token . Echolot::Globals::get()->{'storage'}->get_secret() );
+ my $cut_hash = substr($hash, 0, Echolot::Config::get()->{'hash_len'});
+
+ ($cut_hash eq $received_hash) or
+ Echolot::Log::info("Hash mismatch in '$address'."),
+ return undef;
+
+ return
+ { timestamp => $timestamp,
+ token => $type };
+};
+
+sub send_message(%) {
+ my (%args) = @_;
+
+ defined($args{'To'}) or
+ Echolot::Log::cluck ('No recipient address given.'),
+ return 0;
+ $args{'Subject'} = '(no subject)' unless (defined $args{'Subject'});
+ $args{'Body'} = '' unless (defined $args{'Body'});
+ $args{'From_'} =
+ Echolot::Config::get()->{'my_localpart'}.
+ '@'.
+ Echolot::Config::get()->{'my_domain'};
+ if (defined $args{'Token'}) {
+ $args{'From'} = make_address( $args{'Token'} );
+ } else {
+ $args{'From'} = $args{'From_'};
+ };
+ $args{'Subject'} = 'none' unless (defined $args{'Subject'});
+
+ my @lines = map { $_."\n" } split (/\r?\n/, $args{'Body'});
+
+ open(SENDMAIL, '|'.Echolot::Config::get()->{'sendmail'}.' -f '.$args{'From_'}.' -t')
+ or Echolot::Log::warn("Cannot run sendmail: $!."),
+ return 0;
+ printf SENDMAIL "From: %s\n", $args{'From'};
+ printf SENDMAIL "To: %s\n", $args{'To'};
+ printf SENDMAIL "Subject: %s\n", $args{'Subject'};
+ printf SENDMAIL "\n";
+ for my $line (@lines) {
+ print SENDMAIL $line;
+ };
+ close SENDMAIL;
+
+ return 1;
+};
+
+sub make_monthname($) {
+ my ($month) = @_;
+ my @MON = qw{Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec};
+ return $MON[$month];
+};
+
+sub make_dayname($) {
+ my ($day) = @_;
+ my @WDAY = qw{Sun Mon Tue Wed Thu Fri Sat};
+ return $WDAY[$day];
+};
+
+sub date822($) {
+ my ($date) = @_;
+
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($date);
+ # 14 Aug 2002 17:11:12 +0100
+ return sprintf("%s, %02d %s %d %02d:%02d:%02d +0000",
+ make_dayname($wday),
+ $mday,
+ make_monthname($mon),
+ $year + 1900,
+ $hour,
+ $min,
+ $sec);
+};
+
+sub write_meta_information($%) {
+ my ($file, %data) = @_;
+
+ return 1 unless Echolot::Config::get()->{'write_meta_files'};
+
+ $file .= Echolot::Config::get()->{'meta_extension'};
+ open (F, ">$file") or
+ Echolot::Log::warn ("Cannot open $file: $!."),
+ return 0;
+ if (defined $data{'Expires'}) {
+ my $date = date822($data{'Expires'});
+ print F "Expires: $date\n";
+ };
+ close(F);
+ return 1;
+};
+
+sub escape_HTML_entities($) {
+ my ($in) = @_;
+
+ $in =~ s/&/&amp;/;
+ $in =~ s/"/&quot;/;
+ $in =~ s/</&lt;/;
+ $in =~ s/>/&gt;/;
+
+ return $in;
+};
+
+sub write_HTML_file($$;$%) {
+ my ($origfile, $template_file, $expire, %templateparams) = @_;
+
+ my $operator = Echolot::Config::get()->{'operator_address'};
+ $operator =~ s/@/./;
+
+ for my $lang ( keys %{Echolot::Config::get()->{'templates'}} ) {
+ my $template = HTML::Template->new(
+ filename => Echolot::Config::get()->{'templates'}->{$lang}->{$template_file},
+ strict => 0,
+ die_on_bad_params => 0,
+ global_vars => 1 );
+ $template->param ( %templateparams );
+ $template->param ( CURRENT_TIMESTAMP => scalar gmtime() );
+ $template->param ( SITE_NAME => Echolot::Config::get()->{'sitename'} );
+ $template->param ( separate_rlist => Echolot::Config::get()->{'separate_rlists'} );
+ $template->param ( combined_list => Echolot::Config::get()->{'combined_list'} );
+ $template->param ( thesaurus => Echolot::Config::get()->{'thesaurus'} );
+ $template->param ( fromlines => Echolot::Config::get()->{'fromlines'} );
+ $template->param ( version => Echolot::Globals::get()->{'version'} );
+ $template->param ( operator => $operator );
+ $template->param ( expires => date822( time + $expire ));
+
+ my $file = $origfile;
+ $file .= '.'.$lang unless ($lang eq 'default');
+ $file .= '.html';
+
+ open(F, '>'.$file) or
+ Echolot::Log::warn("Cannot open $file: $!."),
+ return 0;
+ print F $template->output() or
+ Echolot::Log::warn("Cannot print to $file: $!."),
+ return 0;
+ close (F) or
+ Echolot::Log::warn("Cannot close $file: $!."),
+ return 0;
+
+ if (defined $expire) {
+ write_meta_information($file,
+ Expires => time + $expire) or
+ Echolot::Log::debug ("Error while writing meta information for $file."),
+ return 0;
+ };
+ };
+
+ return 1;
+};
+
+sub make_gpg_fds() {
+ my %fds = (
+ stdin => IO::Handle->new(),
+ stdout => IO::Handle->new(),
+ stderr => IO::Handle->new(),
+ status => IO::Handle->new() );
+ my $handles = GnuPG::Handles->new( %fds );
+ return ($fds{'stdin'}, $fds{'stdout'}, $fds{'stderr'}, $fds{'status'}, $handles);
+};
+
+sub readwrite_gpg($$$$$) {
+ my ($in, $inputfd, $stdoutfd, $stderrfd, $statusfd) = @_;
+
+ Echolot::Log::trace("Entering readwrite_gpg.");
+
+ local $INPUT_RECORD_SEPARATOR = undef;
+ my $sout = IO::Select->new();
+ my $sin = IO::Select->new();
+ my $offset = 0;
+
+ Echolot::Log::trace("input is $inputfd; output is $stdoutfd; err is $stderrfd; status is ".(defined $statusfd ? $statusfd : 'undef').".");
+
+ $inputfd->blocking(0);
+ $stdoutfd->blocking(0);
+ $statusfd->blocking(0) if defined $statusfd;
+ $stderrfd->blocking(0);
+ $sout->add($stdoutfd);
+ $sout->add($stderrfd);
+ $sout->add($statusfd) if defined $statusfd;
+ $sin->add($inputfd);
+
+ my ($stdout, $stderr, $status) = ("", "", "");
+
+ my ($readyr, $readyw);
+ while ($sout->count() > 0 || (defined($sin) && ($sin->count() > 0))) {
+ Echolot::Log::trace("select waiting for ".($sout->count())." fds.");
+ ($readyr, $readyw, undef) = IO::Select::select($sout, $sin, undef, 42);
+ Echolot::Log::trace("ready: write: ".(defined $readyw ? scalar @$readyw : 'none')."; read: ".(defined $readyr ? scalar @$readyr : 'none'));
+ for my $wfd (@$readyw) {
+ Echolot::Log::trace("writing to $wfd.");
+ my $written = 0;
+ if ($offset != length($in)) {
+ $written = $wfd->syswrite($in, length($in) - $offset, $offset);
+ }
+ unless (defined ($written)) {
+ Echolot::Log::warn("Error while writing to GnuPG: $!");
+ close $wfd;
+ $sin->remove($wfd);
+ $sin = undef;
+ } else {
+ $offset += $written;
+ if ($offset == length($in)) {
+ Echolot::Log::trace("writing to $wfd done.");
+ close $wfd;
+ $sin->remove($wfd);
+ $sin = undef;
+ }
+ }
+ }
+
+ next unless (defined(@$readyr)); # Wait some more.
+
+ for my $rfd (@$readyr) {
+ if ($rfd->eof) {
+ Echolot::Log::trace("reading from $rfd done.");
+ $sout->remove($rfd);
+ close($rfd);
+ next;
+ }
+ Echolot::Log::trace("reading from $rfd.");
+ if ($rfd == $stdoutfd) {
+ $stdout .= <$rfd>;
+ next;
+ }
+ if (defined $statusfd && $rfd == $statusfd) {
+ $status .= <$rfd>;
+ next;
+ }
+ if ($rfd == $stderrfd) {
+ $stderr .= <$rfd>;
+ next;
+ }
+ }
+ }
+ Echolot::Log::trace("readwrite_gpg done.");
+ return ($stdout, $stderr, $status);
+};
+
+sub crypt_symmetrically($$) {
+ my ($msg, $direction) = @_;
+
+ ($direction eq 'encrypt' || $direction eq 'decrypt') or
+ Echolot::Log::cluck("Wrong argument direction '$direction' passed to crypt_symmetrically."),
+ return undef;
+
+ my $GnuPG = new GnuPG::Interface;
+ $GnuPG->call( Echolot::Config::get()->{'gnupg'} ) if (Echolot::Config::get()->{'gnupg'});
+ $GnuPG->options->hash_init(
+ armor => 1,
+ homedir => Echolot::Config::get()->{'gnupghome'} );
+ $GnuPG->options->meta_interactive( 0 );
+ $GnuPG->passphrase( Echolot::Globals::get()->{'storage'}->get_secret() );
+
+ my ( $stdin_fh, $stdout_fh, $stderr_fh, $status_fh, $handles ) = make_gpg_fds();
+ my $pid =
+ $direction eq 'encrypt' ?
+ $GnuPG->encrypt_symmetrically( handles => $handles ) :
+ $GnuPG->decrypt( handles => $handles );
+ my ($stdout, $stderr, $status) = readwrite_gpg($msg, $stdin_fh, $stdout_fh, $stderr_fh, $status_fh);
+ waitpid $pid, 0;
+
+ if ($direction eq 'encrypt') {
+ (($status =~ /^\[GNUPG:\] BEGIN_ENCRYPTION\s/m) &&
+ ($status =~ /^\[GNUPG:\] END_ENCRYPTION\s/m)) or
+ Echolot::Log::info("GnuPG status '$status' didn't indicate message was encrypted correctly (stderr: $stderr). Returning."),
+ return undef;
+ } elsif ($direction eq 'decrypt') {
+ (($status =~ /^\[GNUPG:\] BEGIN_DECRYPTION\s/m) &&
+ ($status =~ /^\[GNUPG:\] DECRYPTION_OKAY\s/m) &&
+ ($status =~ /^\[GNUPG:\] END_DECRYPTION\s/m)) or
+ Echolot::Log::info("GnuPG status '$status' didn't indicate message was decrypted correctly (stderr: $stderr). Returning."),
+ return undef;
+ };
+
+ my $result = $stdout;
+ $result =~ s,^Version: .*$,Version: N/A,m;
+ return $result;
+};
+
+sub make_garbage() {
+
+ my $file = Echolot::Config::get()->{'dev_urandom'};
+ open(FH, $file) or
+ Echolot::Log::warn("Cannot open $file: $!."),
+ return "";
+ my $random = '';
+ my $want = int(rand(int(Echolot::Config::get()->{'random_garbage'} / 2)));
+ my $i = 0;
+ while ($want > 0) {
+ my $buf;
+ $want -= read(FH, $buf, $want);
+ $random .= $buf;
+ ($i++ > 15 && $want > 0) and
+ Echolot::Log::warn("Could not get enough garbage (still missing $want."),
+ last;
+ };
+ close (FH) or
+ Echolot::Log::warn("Cannot close $file: $!.");
+
+ $random = unpack("H*", $random);
+ $random = join "\n", grep { $_ ne '' } (split /(.{64})/, $random);
+ $random = "-----BEGIN GARBAGE-----\n".
+ $random."\n".
+ "-----END GARBAGE-----\n";
+
+ return $random;
+};
+
+sub read_file($;$) {
+ my ($name, $fail_ok) = @_;
+
+ unless (open (F, $name)) {
+ Echolot::Log::warn("Could not open '$name': $!.") unless ($fail_ok);
+ return undef;
+ };
+ local $/ = undef;
+ my $result = <F>;
+ close (F);
+
+ return $result;
+};
+
+sub cleanup_tmp() {
+ my $tmpdir = Echolot::Config::get()->{'tmpdir'};
+
+ opendir(DIR, $tmpdir) or
+ Echolot::Log::warn("Could not open '$tmpdir': $!."),
+ return undef;
+ my @files = grep { ! /^[.]/ } readdir(DIR);
+ closedir(DIR);
+
+ for my $file (@files) {
+ unlink($tmpdir.'/'.$file) or
+ Echolot::Log::warn("Could not unlink '$tmpdir/$file': $!.");
+ };
+};
+
+1;
+
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/LICENSE b/trunk/LICENSE
new file mode 100644
index 0000000..3cccf38
--- /dev/null
+++ b/trunk/LICENSE
@@ -0,0 +1,17 @@
+Echolot - a Pinger for anonymous remailers.
+
+Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+
+Echolot 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 St, Fifth Floor, Boston, MA 02110-1301 USA
diff --git a/trunk/NEWS b/trunk/NEWS
new file mode 100644
index 0000000..383ccf2
--- /dev/null
+++ b/trunk/NEWS
@@ -0,0 +1,415 @@
+Changes in
+ * Update FSF address in all files.
+
+Changes in version 2.1.8 - 2005-04-25
+ * debian: Redirect init script output to /dev/null in logrotate
+
+Changes in version 2.1.7 - 2004-11-15
+ * debian: Add disable and enable to allowed commands in init
+ script
+ * debian: Create echolot user group in proper gid space.
+ * debian: remove debconf stuff.
+ * handle empty environment better - previously we would whine
+ when HOME or PATH were not set.
+
+Changes in version 2.1.6 - 2004-08-07
+ * Catch a possible use of undefined values in a log trace() call.
+
+Changes in version 2.1.5 - 2004-06-22
+ * Fix use of an illegal filedescriptor when we know of a remailer,
+ but do not have keys for it yet/anymore.
+ * Check if syswrite's return value is defined
+ (also is: do not write to gpg forever, once it has died or
+ closed its stdin).
+ * Tell Mixmaster to create no dummies. It has no remailer keys
+ and reliability information anyway.
+ * Increase the number of remailers that need to list an address
+ before it is added to 6. It was 3 previously.
+ * Make set fetch=<value> work again.
+ * Make disable and enable actually work.
+
+Changes in version 2.1.4 - 2004-06-10
+ * Have new disable and enable commands which are shortcut
+ for set pingit=off showit=off fetch=off and =on respectively.
+ * Accept show=, ping=, and fetchit= in addition to their real
+ names in set.
+ * Do not accept expired or future mixmaster keys.
+ * Print summary on notice level, not info, if it was requested
+ manually.
+ * Remove get_remailers since get_addresses in Echolot::Storage::File
+ does almost the same.
+ * Make sure we do not create empty key hashes in metadata.
+ * gpg interaction cleanup, move a lot of duplicated code to
+ Echolot::Tools.
+ * Filter out mixmaster outputs to stderr that are no problem.
+ * There's a TRACE loglevel now, which is even more noisy than DEBUG.
+ * Changed all pointers to savannah to alioth.
+ * Removed obsolete tools to convert echolot setups during 2.0beta*
+ releases.
+ * Handle situations better in which a ping receives when the last
+ key already is expired, and the remailer no longer exists in
+ the remailers part of the metadata (only in the addresses part).
+ * Add a file that describes how Echolot works.
+
+Changes in version 2.1.3 - 2004-04-20
+ * Improvements to pingd.conf(5) by Colin.
+ * Use 'Apr' instead of 'Arp' for April. closes: DebianBug#243504.
+
+Changes in version 2.1.2 - 2003-11-04
+ * Also ignore testing remailers in the thesaurus.
+ * Also ignore testing remailers in fromlines.
+
+Changes in version 2.1.1 - 2003-11-02
+ * Fix 0/false in Fetch/Ping/Show in summary report.
+ * fork() for calling mixmaster, so we can exec() it
+ rather can using open("| ...") which ivokes a shell.
+ * Catch SIGPIPE and log it as error.
+ * Document thesaurusindexfile.
+ * Minor documentation fixes.
+ * When asked to shut down, exit immediatly after the current running
+ task, even if we have a backlog.
+ * If we run late we should now drop actions where it does not hurt.
+ * Remove random command invocation via pingctr/init script. People
+ should just call pingd directly. Also wait for pingd to really
+ shut down in stop and restart.
+ * Regularily clean up the temp directory.
+ * Send out pings every two hours by default.
+ * Modified statistics generation:
+ We no longer assume a normal distribution of latencies but instead
+ use percentiles when calculating a life probability of an outstanding
+ ping. Also we do not show the mean of latency but the median as
+ this seems to be 'more correct'.
+ Also have different weights for pings based on their age.
+ * Optimized chain pinging: takes less CPU.
+ * Do not list remailers with the 'testing' capability in
+ public stats. Testing indicates a node is not ready for real
+ users' traffic yet.
+ * Publish operator's address on index page.
+ * Refuse to run with euid == 0.
+
+Changes in version 2.1 - 2003-03-03
+ * Minor documentation fixes suggested by Ryan Lackey.
+ * Adjusted some loglevels (several info got downgraded to debug)
+ * Removed --pgp2 in CPunk RSA pings.
+ * Append random garbage to pings so they have different
+ lengths.
+ new options:
+ - random_gabage (default: 8192)
+ Garbage length is chosen uniformly from 0 to random_gabage
+ bytes.
+ - dev_urandom (default: /dev/urandom)
+ where to read garbage from (more generally: non-blocking
+ (low-quality) randomless source)
+ * Chain pinging: Echolot now finds broken chains.
+ new options:
+ - do_chainpings (default: 1)
+ Whether to do chain pinging.
+ - show_chainpings (default: 1)
+ Whether to show the result of our chain pinging in the
+ public stats.
+ Several other settings control the details of chain pinging.
+ You probably don't want or need to tweak them. See
+ the pingd.conf(5) manual page for details.
+ - Also adds the new "sendchainpings" command.
+ * Create an index.txt file in the thesaurus directory which
+ holds id, nick, and address for each remailer in the thesaurus.
+ This helps additional tools to utilize Echolot's data.
+ * Compile a list of default From: lines as well as a list of
+ remailers which allow user supplied From headers.
+ * Print status summary to log daily.
+ - Also adds the new "summary" command.
+ * Reliable and CRLF - a neverending (sad) story.
+ * Handle situations better in which a ping is received when the
+ key already has been expired.
+
+Changes in version 2.0.10 - 2003-02-03
+ * Return undef rather than 0 if we cannot open a Maildir.
+ * Add missing use of Carp to Echolot::Storage::File.pm.
+
+Changes in version 2.0.9 - 2003-01-14
+ * Logging is finally cleaned up. There are two new config options:
+ logfile and loglevel.
+ * Automatically remove stale .pid files.
+ * Trash messages with errors immediately rather than storing them
+ in mailerrordir forever.
+
+Changes in version 2.0.8 - 2003-01-13
+ * Work around a bug that let pingd die in certain random cases when
+ dealing with unuseable PGP keys (like expired keys).
+ Talk with Frank Tobin from GnuPG::Interface fame led to the diagnosis
+ that pingd gets a SIGPIPE since the GnuPG process already exited.
+ * Removed a duplicate line from v2legend.
+ * Allow periods in hostname.
+ * Don't use GnuPG::Interface's recipients when encrypting any more as
+ it is broken with at least GnuPG 1.2.1.
+ * Always use --no-secmem-warning with GnuPG calls.
+ * Also use supported remailers from type2 only remailers.
+ * Reset metadata if status is not defined.
+
+Changes in version 2.0.7 - 2002-12-18
+ * Added upgrade HOWTO.
+
+Changes in version 2.0.6 - 2002-12-18
+ * Have support for translated templates
+ * The templates now make use of CSS.
+ * Minor documentation fixes by Colin Tuckley
+ * thesaurusindexfile and indexfilebasename config values should no
+ longer have the extension (.html) in them
+ * Fix v2 stats for cypherpunk remailers by using right column title.
+ * Use '(no subject)' instead of '' as a subject if none is
+ otherwise chosen.
+
+Changes in version 2.0.5 - 2002-10-25
+ * Only take default parameters if they are not set in
+ pingd.conf (as opposed to set to undef).
+ * Also support setups without user defined mailboxes
+ (recipient_delimiter set to '').
+
+Changes in version 2.0.4 - 2002-10-16
+ * Fix pingd.conf(5) manpage a bit (indention levels).
+
+Changes in version 2.0.3 - 2002-10-12
+ * Set program name according to current action.
+ * Fix a typo that showed up in perl 5.0005_02 (had a , instead of a .
+ in an open() when reading mboxen as input).
+
+Changes in version 2.0.2 - 2002-09-21
+ * Die immeditatly if there is no Version information in metadata.
+ * Make regular backups of metadata and rotate them properly.
+
+Changes in version 2.0.1 - 2002-09-21
+ * Store unkown fields from mix summary lines and put them in keyrings.
+
+Changes in version 2.0 - 2002-09-17
+ * New version number, this is 2.0, really!
+ * Minor spelling fixes.
+
+Changes in version 2.0rc3 - 2002-09-12
+ * Write SENDMAIL to mix.cfg
+ * Add sendpings command.
+ * Only decrease a remailer's ttl during requesting -conf if
+ it was requested by the usualy timer run and not by the
+ user.
+ * Template cleanup: fix a link and add links to .txt versions
+ to front page.
+ * Have install-perl-modules tool.
+ * Spelling fixes.
+ * seperate_rlists was renamed to separate_rlists.
+ * README was improved.
+
+Changes in version 2.0rc2 - 2002-09-08
+ * Reopen stdin to /dev/null instead of closing it to avoid
+ perl 5.8 warnings.
+
+Changes in version 2.0rc1 - 2002-09-05
+ * new version number.
+ * Random spelling fixes.
+ * Do not show hidden remailers in thesaurus.
+
+Changes in version 2.0beta34 - 2002-09-04
+ * Make ping/request time more random.
+ * Encrypt pings (symmetrically), so that dup detection of some
+ remailers (austria) fails and pings get processed.
+ * Write total number of unique remailers to echolot.html.
+
+Changes in version 2.0beta33 - 2002-08-23
+ * Scheduler fixes (inserted jobs for one time processing got requeued
+ over and over again according to their interval).
+ * Give a short summary about current stats on index page.
+
+Changes in version 2.0beta32 - 2002-08-23
+ * Fix a major bug introduced in 2.0beta31 that resulted in no
+ remailer-xxx queries beeing sent out.
+
+Changes in version 2.0beta31 - 2002-08-21
+ * Have a consistent name for the ~/echolot directory in README.
+ * If you request keyconf from only a few remailers, more requests could
+ have been sent. Fixed that.
+ * Write REMAIL n to mix.cfg - apparently it defaults to yes, which
+ means Mixmaster will want to create keys.
+ * Fix random typos.
+ * Fix pingctl script.
+
+Changes in version 2.0beta30 - 2002-08-15
+ * Write NAME and ADDRESS to mix.cfg - mix cannot figure it out
+ if detached.
+
+Changes in version 2.0beta29 - 2002-08-14
+ * Create .meta files with the expiry date of pages.
+ * Also put the expiry date in HTML meta headers.
+ * Make indexfilebasename a configure option.
+ * Code cleanup: renamed some functions.
+
+Changes in version 2.0beta28 - 2002-08-13
+ * Write a standard mix.cfg configuration file for mixmaster.
+ It only sets PUBRING and TYPE2LIST. Needed for systems where
+ those values are overriden by systemwide defaults.
+
+Changes in version 2.0beta27 - 2002-08-13
+ * Some small fixes to the Debian Package
+
+Changes in version 2.0beta26 - 2002-08-12
+ * Fix a few typos in the echolot.html template.
+ * Added /etc/echolot/pingd.conf to the list of configfiles.
+ * Have a debian/ directory to build a Debian package.
+ * Config option mailindir was renamed to mailin. You now
+ can also point it to a mbox format mailbox.
+ * Have --quiet.
+ * Stricter permissions for most newly created directories (go-rwx).
+
+Changes in version 2.0beta25 - 2002-08-10
+ * Produce echolot.html, an index file for echolot results.
+
+Changes in version 2.0beta24 - 2002-08-10
+ * Remove »x« from end of pubring.mix summary lines.
+
+Changes in version 2.0beta23 - 2002-08-07
+ * Unlinking Thesaurus files works now.
+ * Detach correctly now.
+
+Changes in version 2.0beta22 - 2002-08-05
+ * Actually use the sane basedir. *sigh*
+
+Changes in version 2.0beta21 - 2002-08-05
+ * Using a sane basedir by default. You no longer need to specify
+ it in pingd.conf.
+
+Changes in version 2.0beta20 - 2002-08-02
+ * Added pingctl wrapper to tools.
+ * echolot --help now gives a brief list of commands.
+ * Added acknowledgements.
+
+Changes in version 2.0beta19 - 2002-07-29
+ * Build keyrings in results.private too
+
+Changes in version 2.0beta18 - 2002-07-23
+ * Allow for inclusion of broken chain reports and same operator/machine
+ lists in stats pages. Broken chain reports are not generated
+ automatically; this is planned for a later release. For now you need
+ to supply this information in the files broken1.txt, broken2.txt, and
+ sameop.txt.
+
+Changes in version 2.0beta17 - 2002-07-22
+ * You no longer need an extra Mixmaster installation for your pinger.
+ Echolot can make use of any Mixmaster binary you alread have
+ installed.
+ * The config hash »Pinger::Mix« is obsolete now. Please set
+ »mixmaster« to the path of your mix executeable. Echolot now
+ uses its own mix directory below echolothome by default (config
+ option mixhome).
+ * The default gnupghome has changed from »gnupg« to »gnupghome«.
+ * New config option »gnupg«.
+ * Not the current time but the scheduled start time is used when
+ deciding which remailers to ping. This is nice if we get delayed for
+ whatever reason.
+ * Changed URL in templates to point to www.palfrader.org/echolot
+ instead of the savannah page.
+
+ How to upgrade (assuming you have a default configuration):
+ ./pingd stop
+ mv gnupg gnupghome
+ vi pingd.conf (remove Pinger::Mix, set mixmaster;
+ cf. pingd.conf.sample )
+ ./pingd [...] start
+
+Changes in version 2.0beta16 - 2002-07-17
+ * Do not send all pings for the same remailer at the same time
+ * The cheap hashing function that was used for determining when to ping
+ was replaced by md5. The local secret also is an input to the
+ function so not all echolot pingers ping the same remailer at the
+ same time.
+ * In the distribution the pingd.conf file has been renamed to pingd.conf.
+ So it should be possible to just untar the new tar.gz over the old
+ installation (you have a backup anyway, don't you?).
+ * The getkeyconf command takes optional addresses to request config
+ data from.
+ * The getkeyconf config option was replaced by getkeyconf_interval and
+ getkeyconf_every_nth_time. Not all requests are sent at the same time
+ any more.
+ * Stats can be sorted by latency rather than nick. Set
+ stats_sort_by_latency if you want that.
+
+Changes in version 2.0beta15 - 2002-07-16
+ * Have echolot version in stats HTML pages
+ * Random documentation fixes
+ * Make it runs with older perls (5.005_03 is tested)
+ - always pass two arguments to mkdir()
+ - import SEEK_ constants from POSIX rather than Fcntl
+ - do not use warnings
+
+Changes in version 2.0beta14 - 2002-07-16
+ * Added commands buildstats buildkeys and buildthesaurus
+ * Added legend to templates (Orange)
+ * Thesaurus building failed when an id did not return a
+ valid remailer. A check was there bug it was wrong
+ * Have pingd.conf.5 manpage documenting all options
+
+Changes in version 2.0beta13 - 2002-07-13
+ * Have correct title tags and some layout changes in the HTML templates
+ * Encode HTML Entities in HTML Stats
+ * Only show valid remailers in Thesaurus
+ * Support »not a remailer« reply to remailer-xxx
+ * Make sure only the right keys are exported to the pgp keyrings
+ * Do not skip key if GnuPG returns something in stderr with
+ remailer-key replies
+
+Changes in version 2.0beta12 - 2002-07-11
+ * Cut strings when they do not fit in the table formats
+ * Sort type2.list/pubring.mix
+
+Changes in version 2.0beta11 - 2002-07-11
+ * Don't run in Taint mode anymore per default.
+ If you still want it, trimm your PATH to something acceptable
+ for perl and add »-T« to the first line of pingd.
+
+Changes in version 2.0beta10 - 2002-07-11
+ * Don't reset PATH to /usr/bin:/bin any more (Yes, again)
+ * Documentation updates
+
+Changes in version 2.0beta9 - 2002-07-11
+ * Don't reset PATH to /usr/bin:/bin any more
+ * Use confess or cluck instead of croak in some places.
+
+Changes in version 2.0beta8 - 2002-07-10
+ * Fix public clist.
+
+Changes in version 2.0beta7 - 2002-07-10
+ * fixed a stupid syntax bug.
+
+Changes in version 2.0beta6 - 2002-07-10
+ * Did away with Mail::Internet. Using local sendmail binary only.
+ This also means the smarthost config option is obsolete and there
+ is a sendmail config option now.
+
+Changes in version 2.0beta5 - 2002-07-10
+ * Thesaurus filenames changed from nn-foo to nn.foo.
+ Use the change-thesaurus-filenames script in the tools
+ directory to convert your current thesaurus if you wish
+ to keep the data.
+ * Seperate rlists for rsa, dsa and plaintext pings are
+ supported now. Enable seperate_rlists in pingd.conf.
+ * Have a combined list. Enable with combined_list in pingd.conf.
+
+Changes in version 2.0beta4 - 2002-07-10
+ * Minor documentation fixes
+ * Added --process switch
+ * Die if metadata or conf data cannot be parsed
+ * Build manpage into extra file
+
+Changes in version 2.0beta3 - 2002-07-10
+ * Fixed a small bug with writing the metadata so that it could not be
+ parsed again
+
+Changes in version 2.0beta2 - 2002-07-10
+ * Moved from XML to Data::Dumper
+ Use convert-xml-to-datadumper in the tools directory to
+ convert your setup:
+ pingd stop
+ mv pingd.conf pingd.conf.old
+ mv data/metadata data/metadata.old
+ convert-xml-to-datadumper CONFIG < pingd.conf.old > pingd.conf
+ convert-xml-to-datadumper METADATA < data/metadata.old > data/metadata
+ pingd start
+
+Changes in version 2.0beta1 - 2002-07-07
+ * Initial public beta test
diff --git a/trunk/README b/trunk/README
new file mode 100644
index 0000000..6d62f2e
--- /dev/null
+++ b/trunk/README
@@ -0,0 +1,319 @@
+$Id$
+#####################################################################
+## R E A D M E F O R E C H O L O T ###########################
+#####################################################################
+
+| Echolot, das: (German) sonic depth finder
+
+PURPOSE
+-------
+Echolot is a Pinger for anonymous remailers such as Mixmaster
+
+A Pinger in the context of anonymous remailers is a program that
+regularly sends messages through remailers to determine their status.
+Based on the responses, the Pinger calculates reliability statistics
+which may be used by remailer clients to choose a chain of remailers to
+use.
+
+Furthermore, Echolot collects configuration parameters and keys of
+remailers and offers the collected information in a format readable by
+remailer clients. This helps reduce the administration effort required
+to use or host remailers.
+
+This is Echolot2. Besides the name, author, and purpose, this software
+has nothing to do with Echolot1. Echolot2 has been written from
+scratch.
+
+LICENSE
+-------
+Please see the file named "LICENSE".
+
+
+REQUIREMENTS
+------------
+
+o GnuPG (1.0.7 or higher required)
+o Mixmaster http://mixmaster.sourceforge.net/
+o Perl (5.8 or higher suggested)
+o a local Mail Transfer Agent (MTA)
+o Procmail (recommended)
+
+The following perl modules:
+o HTML::Template
+o GnuPG::Interface (0.33 or higher required)
+o Data::Dumper (should be part of perl-base)
+o Digest::MD5 (included in perl 5.8 or higher)
+
+INITIAL SETUP
+-------------
+
+o Verify that gpg 1.0.7 or later is installed:
+ # gpg -version
+
+o If the required perl modules above are not yet installed on your
+ system, or if you are not sure if these perl libraries are installed,
+ please install or upgrade the libraries as follows. Even if you
+ already have these libraries installed, there is no harm in following
+ this installation procedure anyway.
+
+ Execute the install-perl-modules script from the tools directory. It
+ makes use of the CPAN module to check whether you have the required
+ modules installed and if not downloads and installs them for you.
+ # tools/install-perl-modules
+
+ If this command reports errors, please verify that you are using perl
+ 5.8 or higher and are connected to the Internet.
+
+ [Note: if you operating system already has packages with the required
+ libraries than those generally are preferred since they integrate better with
+ your system.
+
+ On Debian for instance the following command can be used to get
+ everything that is required:
+ # apt-get install libgnupg-interface-perl libhtml-template-perl
+ ]
+
+o Create a new user named »pinger« (You can use a different user name if
+ you so desire. The remainder of this document assumes that Echolot has
+ been installed as user »pinger«).
+
+o Copy all Echolot files and directories to the directory
+ ~pinger/echolot.
+
+o Copy the pingd.conf.sample file to pingd.conf.
+
+o Check the homedir setting and set sitename in pingd.conf to match your
+ host.
+
+o If the Mixmaster executable »mix« is not in your PATH, set the
+ »mixmaster« config option in pingd.conf to point to your local
+ installation of mixmaster. Echolot can use any accessible mixmaster
+ binary, such as the mix binary of a remailer that may be installed on
+ the same machine. (Frequently found in /home/remailer/Mix/mix.)
+
+ Echolot will not share the Mixmaster pool or key rings with the
+ existing Mixmaster installation. Instead, Echolot uses pools and
+ keyrings as specified by the mixhome configuration option.
+
+ If you prefer, you can build a second Mixmaster binary for the
+ exclusive use of Echolot and place that binary in /home/pinger/Mix.
+ There is no need to put configuration information or key rings into
+ that directory - they will not get used.
+
+o If the GnuPG executable »gpg« is not in your PATH, set the »gnupg«
+ configuration option in pingd.conf.
+
+o Set my_localpart and my_domain in pingd.conf to the appropriate values
+ for your pinger. Mail to my_localpart@my_domain needs to reach
+ Echolot.
+
+o Make sure your MTA supports user defined mailboxes to ensure that
+ email addressed to my_localpart+anything@my_domain will reach Echolot.
+
+ If your MTA uses a character other than »+« to indicate a user defined
+ extension, set recipient_delimiter accordingly in pingd.conf.
+
+ If you are using postfix as your MTA, adding the following line to
+ postfix.s main.cf file will enable user defined mailboxes:
+ recipient_delimiter = +
+
+ If you are using an MTA other than postfix, consult your MTA's
+ documentation to determine how to enable user defined mailboxes.
+
+ If it is not possible for you to have user defined mailboxes set
+ recipient_delimiter to the empty string "" in pingd.conf. Echolot
+ will then work around it (This is _not_ recommended).
+
+o Echolot can read its incoming mail either from an mbox format mailbox
+ or from a Maildir. The latter is preferred for technical reasons since
+ a Maildir does not require file locking.
+
+ Echolot's »mailin« configuration variable defines from which location
+ mail is being read. The variable defaults to »mail«. If this is a
+ directory, Maildir is assumed, otherwise mbox format is assumed.
+
+ If you can only use mbox format for incoming email:
+ Change the »mailin« config option to »/var/spool/pinger« (or
+ wherever incoming email for user pinger is being spooled on your
+ system).
+
+ If you are able to use Maildir (recommended):
+ Mail will be delivered to /home/pinger/echolot/mail, a Maildir
+ mailbox.
+
+ Create Echolot's Maildir:
+ # mkdir /home/pinger/echolot/mail
+
+ Make sure the directory owned by pinger:
+ # chown pinger. /home/pinger/echolot/mail
+
+ If you are using postfix as your MTA, add one of the following lines
+ to postfix's main.cf file to enable the use of procmail depending
+ where on your system procmail is located.
+ mailbox_command = /usr/bin/procmail
+ mailbox_command = /usr/local/bin/procmail
+
+ Reload postfix for the changes to main.cf to take effect.
+ # postfix reload
+
+ With procmail now active in your MTA, save the following two lines
+ as /home/pinger/.procmailrc to ensure that mail for Echolot will be
+ stored in Echolot.s Maildir:
+
+ :0
+ $HOME/echolot/mail/
+
+ (CAVEAT: the trailing slash is significant and may not be
+ omitted!)
+
+ If you are using qmail as your MTA, do the following:
+ # echo "./echolot/mail/" > .qmail
+ # touch .qmail-default
+
+o Finally, double-check to make sure that all of Echolot.s files and
+ directories are owed by user pinger.
+
+
+
+RUNNING ECHOLOT FOR THE FIRST TIME
+----------------------------------
+
+o Obtain the email addresses of 4 reliable remailers. Once connected to
+ the remailer network, Echolot will over time learn about other
+ remailers in operation. You can find a list of email addresses of
+ reliable remailers to seed Echolot.s auto-discovery feature at
+ http://www.noreply.org/echolot/rlist2.txt
+
+ This list was created by the Echolot program.
+
+o As user »pinger«, open two terminal windows.
+
+o Change into the directory where echolot is kept.
+ $ cd echolot
+
+o In the first terminal window, type:
+ [ you may want to set the log level to 'debug' in pingd.conf
+ to get an idea what exactly Echolot is doing ]
+
+ $ ./pingd --detach start
+ $ tail -f pingd.log
+
+o In the second terminal window, type:
+ $ ./pingd add <address1> <address2> <address3> ...
+
+ You can also use the following shell magic to add all addresses from
+ an existing rlist.txt or mlist.txt:
+ $ grep \$remailer rlist.txt | cut -f 2 -d \< | cut -f 1 -d \> |
+ xargs ./pingd add
+
+ Monitor the first terminal in which you started pingd. You should see
+ mention of email addresses being added.
+
+o In the second terminal window, execute
+ $ ./pingd getkeyconf
+
+ This will request remailer key and configuration files from the
+ remailers that you added in the previous step.
+
+o pingd can be stopped with the command
+
+ $ ./pingd stop
+
+
+
+VERIFYING ECHOLOT's OPERATION
+-----------------------------
+
+o Wait a few minutes for Echolot to receive results back from the
+ remailers that have been pinged
+
+o Look at one of Echolot.s result pages with the web browser of your
+ choice. For example:
+
+ $ cd /home/pinger/echolot/results
+ $ lynx mlist2.html
+
+ The file should list several remailers.
+
+ NOTE: Results for Type I remailers can be expected within minutes.
+ Results for Type II remailers may take up to an hour to appear.
+
+
+
+DAY-TO-DAY OPERATION
+--------------------
+
+o To run Echolot in the background, run
+ $ ./pingd --detach start
+
+o You can monitor the log file to obtain debugging output:
+ $ tail -f pingd.conf
+
+ Do not forget to set the appropriate log level in pingd.conf.
+
+o The tools directory contains the »pingctl« wrapper for Echolot.
+ The wrapper takes care of checking ulimits, userid, and cd'ing to the
+ correct directory.
+
+ To start Echolot at system startup, install this wrapper as an init
+ script in /etc/init.d or /usr/local/etc/rc.d, or wherever your
+ operating system stores System V-style initialization scripts. You can
+ link this wrapper from the runlevel directories if your init is SysV
+ style.
+
+o Echolot puts its stats in the result directory. Echolot also produces
+ an index file named echolot.html. If you want to use echolot.html as
+ your webpage.s index page, create a symbolic link.
+ $ ln -sf echolot.html index.html
+
+ Alternatively, you can set the indexfilebasename option in pingd.conf
+ to »index« (no .html extension).
+
+o Echolot additionally produces .meta files by default. These files
+ include extra headers that your http server should send to clients. If
+ you are using Apache as your web sever, you can load the mod_cern_meta
+ Apache module and set MetaFiles to "on". Please ensure that Apache's
+ MetaSuffix matches your meta_extension setting (".meta" by default)
+ and that MetaDir is set to ".". See your web server's documentation
+ for more information on meta files.
+
+
+
+CONFIGURATION
+-------------
+
+Consult the pingd.conf.5 manpage for documentation on the available
+configuration options.
+
+To obtain all available configuration options and their current value
+run:
+ $ ./pingd dumpconf
+
+You will need to restart pingd after making changes to pingd.conf for
+the changes to take effect.
+
+
+
+CAVEATS
+-------
+
+Echolot will keep open all ping and metadata files. This means it needs
+quite a few file descriptors (about 2 * total keys or 6 to 8 * remailers
+plus some for perl). If you have a very strict ulimit for open files you
+need to increase it. A ulimit of 512 should suffice. Obscure errors
+experienced might be caused by a ulimit that has been set too low.
+
+Please report bugs and feature requests at
+ http://alioth.debian.org/projects/echolot/
+
+The Echolot homepage can be found at
+ http://www.palfrader.org/echolot/
+
+
+
+ACKNOWLEDGEMENTS
+----------------
+ Orange Admin for contributing ideas and templates
+ Lucky Green for (re)writing docs
+ BiKiKii Admin for valuable feedback
+ All testers of Echolot.
diff --git a/trunk/TODO b/trunk/TODO
new file mode 100644
index 0000000..b1b9e97
--- /dev/null
+++ b/trunk/TODO
@@ -0,0 +1,35 @@
+$Id$
+
+Legend:
+ - Not done
+ * Top priority
+ . Partially done
+ o Done
+ D Deferred
+ X Abandoned
+
+ - updated information with from lines
+ - don't send that many messages with From Headers
+ - optionally log messages wich raise problems
+ - close and open files instead of keeping all the files open
+ - make 'not a remailer' check not that strict
+
+can be done later:
+ - allow capsstring overrides/additions
+ - check gnupg version number on startup
+ - chain preload and postload
+ D have a way to always have certain keys in the keyring
+ (manually add nym's keyrings)
+ This is not necessary if we add real nymserv pinging
+ D rotate secret
+ D parse bounces and mark pings as dead immediatly
+ D separate keys in remailer-key reply if they share the same armor (cmeclex is
+ the only one doing this. AARG)
+ D query other stat pages for new remailers
+ D ping nymservers
+ D ping mail2news gates
+ D ping remailer -> usenet, summarize from lines
+ X have different stats philosophies (pessimist, optimist..)
+ X get rid of dependency on mix
+ X get rid of dependency on gnupg
+ X have a timeout on GPG calls (nonblocking IO should solve the problem)
diff --git a/trunk/UPGRADE b/trunk/UPGRADE
new file mode 100644
index 0000000..0d0ccd8
--- /dev/null
+++ b/trunk/UPGRADE
@@ -0,0 +1,21 @@
+Upgrading checklist for Echolot
+$Id$
+
+- Download the latest .tar.gz from <URL:http://www.palfrader.org/echolot/>.
+- Verify the signature.
+- Stop the running pingd.
+- Make sure it no longer runs.
+- Make a backup of your entire echolot directory. really.
+- Untar the new Echolot release to a new directory and copy your
+ data directory and pingd.conf there.
+- Replace your old Echolot directory with the new tree.
+- Start the new pingd and verify it's working properly.
+- Congratulations.
+
+
+Notes for upgrades from a version earlier than 2.0.6
+ - If you have specified thesaurusindexfile and/or indexfilebasename in your
+ Echolot configuration file, please remove the .html extension from that
+ setting.
+
+EOF
diff --git a/trunk/debian/README.Debian b/trunk/debian/README.Debian
new file mode 100644
index 0000000..eb3218e
--- /dev/null
+++ b/trunk/debian/README.Debian
@@ -0,0 +1,26 @@
+Echolot for Debian
+------------------
+
+Make sure mail to the Echolot pinger reaches /var/mail/echolot (or configure
+echolot to read it from any other location). Please really check that mail to
+the configured domain works. Any bounces will annoy dozends of remailer
+operators.
+
+To send commands to pingd it's best to use the /etc/init.d/echolot script.
+It takes care that pingd is only called as the correct user. See pingd(1) for a
+list of commands and their description.
+
+If you run this pinger please consider publishing the results so that other
+people benefit from it. Announcing the URL to the remailer operators' list
+<remops@freedom.gmsociety.org>, the alt.privacy.anon-server Usenet newsgroup
+and sending a mail to pingers@palfrader.org would be apprechiated.
+
+Since many users installed Echolot without considering its implications the
+default setup is now to no longer start the pingd in the default installation.
+To actually enable it please modify /etc/default/echolot.
+
+If you want to run a pinger, please configure /etc/echolot/pingd.conf and
+/etc/default/echolot. Then start echolot using /etc/init.d/echolot start
+and add some addresses to it: /etc/default/echolot add remailer@example.com.
+
+ -- Peter Palfrader <weasel@debian.org>, Sun, 14 Nov 2004 23:31:24 +0100
diff --git a/trunk/debian/changelog b/trunk/debian/changelog
new file mode 100644
index 0000000..b6fea78
--- /dev/null
+++ b/trunk/debian/changelog
@@ -0,0 +1,299 @@
+echolot (2.1.8-4) unstable; urgency=low
+
+ * Properly quote arguments to su -c, since its behaviour has changed.
+ * Update to debhelper compatibility level 4, no other changes required
+ (we were already build depending on debhelper >= 4.1.16).
+
+ -- Peter Palfrader <weasel@debian.org> Mon, 6 Mar 2006 15:54:57 +0100
+
+echolot (2.1.8-3) unstable; urgency=low
+
+ * Increase Standards-Version to 3.6.2 from 3.6.1.
+ * Add dependency on adduser since we need it in postinst.
+ * Fix format of chown argument from echolot.adm to echolot:adm.
+ * Update FSF address in debian/copyright.
+
+ -- Peter Palfrader <weasel@debian.org> Sun, 9 Oct 2005 23:26:28 +0200
+
+echolot (2.1.8-2) unstable; urgency=low
+
+ * Since we removed all the debconf templates in 2.1.7-1 we don't need
+ to depend on debconf at all. If it is installed however we still
+ purge our debconf database on upgrades (closes: #331811).
+
+ -- Peter Palfrader <weasel@debian.org> Sun, 9 Oct 2005 23:17:48 +0200
+
+echolot (2.1.8-1) unstable; urgency=low
+
+ * New upstream version:
+ - redirect init script output to /dev/null in logrotate.
+
+ -- Peter Palfrader <weasel@debian.org> Mon, 25 Apr 2005 02:13:29 +0200
+
+echolot (2.1.7-1) unstable; urgency=low
+
+ * New upstream version:
+ - remove debconf stuff.
+ - create echolot group in proper gid space.
+ - modify init script to allow 'enable' and 'disable' command.
+
+ -- Peter Palfrader <weasel@debian.org> Sun, 14 Nov 2004 23:33:41 +0100
+
+echolot (2.1.6-2) unstable; urgency=low
+
+ * Add disable and enable to allowed commands in init script.
+
+ -- Peter Palfrader <weasel@debian.org> Sat, 25 Sep 2004 16:49:11 +0200
+
+echolot (2.1.6-1) unstable; urgency=medium
+
+ * New upstream version, catch one use of an undefined variable in
+ logging.
+
+ -- Peter Palfrader <weasel@debian.org> Sat, 7 Aug 2004 16:00:52 +0200
+
+echolot (2.1.5-1) unstable; urgency=low
+
+ * New upstream version.
+
+ -- Peter Palfrader <weasel@debian.org> Tue, 22 Jun 2004 11:40:01 +0200
+
+echolot (2.1.4-1) unstable; urgency=low
+
+ * New upstream version.
+ * Update debian/copyright to include 2004.
+ * Add dutch translation for debconf templates (closes: #249406).
+ * Update Standards-Version to 3.6.1.
+ * debian/rules: there is no lib/ to copy any longer.
+
+ -- Peter Palfrader <weasel@debian.org> Thu, 10 Jun 2004 15:47:52 +0200
+
+echolot (2.1.3-1) unstable; urgency=low
+
+ * New upstream version.
+ closes: #243504 echolot: typo in month abbreviations
+ * Document exim's local_part_suffix in the recipient_delimiter
+ debconf question.
+ closes: #243496 Exim4 mailbox delimiter
+
+ -- Peter Palfrader <weasel@debian.org> Tue, 20 Apr 2004 13:42:32 +0200
+
+echolot (2.1.2-1) unstable; urgency=low
+
+ * New upstream version.
+
+ -- Peter Palfrader <weasel@debian.org> Tue, 4 Nov 2003 05:16:07 +0100
+
+echolot (2.1.1-1) unstable; urgency=low
+
+ * New upstream version.
+
+ -- Peter Palfrader <weasel@debian.org> Sun, 2 Nov 2003 07:03:43 +0100
+
+echolot (2.1-2) unstable; urgency=low
+
+ * debconf internationialization.
+ Thanks to Christian Perrier for providing the patches and information.
+ closes: #212885 [INTL:fr] French translation of gettext debconf templates
+ closes: #211633 [INTL] Please switch to gettext-based debconf templates
+
+ -- Peter Palfrader <weasel@debian.org> Sun, 12 Oct 2003 12:15:38 +0200
+
+echolot (2.1-1) unstable; urgency=low
+
+ * New upstream version.
+
+ -- Peter Palfrader <weasel@debian.org> Mon, 3 Mar 2003 16:42:42 +0100
+
+echolot (2.0.10-1) unstable; urgency=low
+
+ * New upstream version.
+
+ -- Peter Palfrader <weasel@debian.org> Mon, 3 Feb 2003 21:11:42 +0100
+
+echolot (2.0.9-1) unstable; urgency=medium
+
+ * New upstream version.
+ * Remove link from output to /var/log/echolot/echolot.log
+ since pngd now has some sort of saner logging.
+
+ -- Peter Palfrader <weasel@debian.org> Tue, 14 Jan 2003 08:08:41 +0100
+
+echolot (2.0.8-1) unstable; urgency=medium
+
+ * New upstream version.
+ Fixes evil bug where pingd receives SIGPIPE because GnuPG already exited
+ because of unuseable keys.
+
+ -- Peter Palfrader <weasel@debian.org> Mon, 13 Jan 2003 15:25:27 +0100
+
+echolot (2.0.7-1) unstable; urgency=low
+
+ * New upstream version.
+
+ -- Peter Palfrader <weasel@debian.org> Wed, 18 Dec 2002 19:37:08 +0100
+
+echolot (2.0.6-1) unstable; urgency=low
+
+ * New upstream version.
+ * Fixed init.d script: s,devnull,dev/null,
+ * lintian: init-script-suggests-versioned-depends postinst on debconf.
+
+ -- Peter Palfrader <weasel@debian.org> Wed, 18 Dec 2002 18:44:27 +0100
+
+echolot (2.0.5-2) unstable; urgency=low
+
+ * Add sendpings command to init script.
+ * Have Echolot disabled by default in /etc/default/echolot.
+ * Add some more comments to README.Debian.
+ * Also process pending commands upon pingd startup.
+
+ -- Peter Palfrader <weasel@debian.org> Tue, 12 Nov 2002 02:06:17 +0100
+
+echolot (2.0.5-1) unstable; urgency=low
+
+ * New upstream version (supports use without user defined mailboxes).
+ * Make recipient delimiter configured by debconf.
+
+ -- Peter Palfrader <weasel@debian.org> Fri, 25 Oct 2002 12:54:44 +0200
+
+echolot (2.0.4-3) unstable; urgency=low
+
+ * Add a debconf note about user defined mailboxes being required
+ (foo+bar@example.com).
+
+ -- Peter Palfrader <weasel@debian.org> Thu, 24 Oct 2002 20:31:04 +0200
+
+echolot (2.0.4-2) unstable; urgency=low
+
+ * Change Depends from libdigest-MD5-perl to libdigest-md5-perl
+ (capitalization).
+ * Changed Standards-Version to 3.5.7 (no changes).
+
+ -- Peter Palfrader <weasel@debian.org> Thu, 24 Oct 2002 13:40:41 +0200
+
+echolot (2.0.4-1) unstable; urgency=low
+
+ * New upstream version.
+ * Now that mixmaster got added to the main archive, finally upload
+ Echolot to Debian as well (closes: #164459).
+
+ -- Peter Palfrader <weasel@debian.org> Wed, 16 Oct 2002 10:25:41 +0200
+
+echolot (2.0.3-3) unstable; urgency=low
+
+ * Remove Suggests: procmail.
+ * Copy new description from README to control.
+ * Updated remailer list.
+ * Use a more correct directory structure
+ (usr/lib/echolot -> usr/share/perl5/)
+
+ -- Peter Palfrader <weasel@debian.org> Sat, 12 Oct 2002 14:43:28 +0200
+
+echolot (2.0.3-2) unstable; urgency=low
+
+ * Fix config script to not screw up domainname on upgrades.
+
+ -- Peter Palfrader <weasel@debian.org> Sat, 12 Oct 2002 12:05:28 +0200
+
+echolot (2.0.3-1) unstable; urgency=low
+
+ * Changed Section from contrib/mail to (main/)mail as mimxaster is
+ in main now.
+ * New upstream version.
+
+ -- Peter Palfrader <weasel@debian.org> Sat, 12 Oct 2002 01:10:18 +0200
+
+echolot (2.0.2-1) unstable; urgency=low
+
+ * New upstream version.
+
+ -- Peter Palfrader <weasel@debian.org> Sat, 21 Sep 2002 05:11:37 +0200
+
+echolot (2.0.1-1) unstable; urgency=low
+
+ * New upstream version.
+
+ -- Peter Palfrader <weasel@debian.org> Sat, 21 Sep 2002 03:45:14 +0200
+
+echolot (2.0-1) unstable; urgency=low
+
+ * New upstream version:
+ This one's called 2.0.
+
+ -- Peter Palfrader <weasel@debian.org> Tue, 17 Sep 2002 06:17:43 +0200
+
+echolot (000.2.0rc3-1) unstable; urgency=low
+
+ * New upstream version.
+
+ -- Peter Palfrader <weasel@debian.org> Fri, 13 Sep 2002 01:16:21 +0200
+
+echolot (000.2.0rc2-1) unstable; urgency=low
+
+ * New upstream version.
+
+ -- Peter Palfrader <weasel@debian.org> Sun, 8 Sep 2002 20:16:31 +0200
+
+echolot (000.2.0rc1-1) unstable; urgency=low
+
+ * New version number.
+
+ -- Peter Palfrader <weasel@debian.org> Thu, 5 Sep 2002 16:51:42 +0200
+
+echolot (000.2.0beta34-1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Peter Palfrader <weasel@debian.org> Wed, 4 Sep 2002 13:22:13 +0200
+
+echolot (000.2.0beta33-1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Peter Palfrader <weasel@debian.org> Fri, 23 Aug 2002 09:54:10 +0200
+
+echolot (000.2.0beta32-1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Peter Palfrader <weasel@debian.org> Fri, 23 Aug 2002 08:05:26 +0200
+
+echolot (000.2.0beta31-1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Peter Palfrader <weasel@debian.org> Wed, 21 Aug 2002 21:45:37 +0200
+
+echolot (000.2.0beta30-1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Peter Palfrader <weasel@debian.org> Thu, 15 Aug 2002 05:28:11 +0200
+
+echolot (000.2.0beta29-1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Peter Palfrader <weasel@debian.org> Thu, 15 Aug 2002 00:54:31 +0200
+
+echolot (000.2.0beta28-1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Peter Palfrader <weasel@debian.org> Tue, 13 Aug 2002 07:58:31 +0200
+
+echolot (000.2.0beta27-1) unstable; urgency=low
+
+ * New upstream version
+ * Set the mixmaster binary path.
+ * Use --quiet when starting echolot.
+
+ -- Peter Palfrader <weasel@debian.org> Tue, 13 Aug 2002 02:49:45 +0200
+
+echolot (000.2.0beta26-1) unstable; urgency=low
+
+ * Initial Release.
+
+ -- Peter Palfrader <weasel@debian.org> Mon, 12 Aug 2002 05:44:24 +0200
+
diff --git a/trunk/debian/control b/trunk/debian/control
new file mode 100644
index 0000000..2ccd597
--- /dev/null
+++ b/trunk/debian/control
@@ -0,0 +1,21 @@
+Source: echolot
+Section: mail
+Priority: extra
+Maintainer: Peter Palfrader <weasel@debian.org>
+Build-Depends-Indep: debhelper (>= 4.1.16)
+Standards-Version: 3.6.2
+
+Package: echolot
+Architecture: all
+Depends: ${shlibs:Depends}, gnupg (>= 1.0.7), postfix | mail-transport-agent, mixmaster, libdigest-md5-perl, libhtml-template-perl, libgnupg-interface-perl (>= 0.33), adduser
+Description: Pinger for anonymous remailers such as Mixmaster
+ A Pinger in the context of anonymous remailers is a program that
+ regularly sends messages through remailers to determine their status.
+ Based on the responses, the Pinger calculates reliability statistics
+ which may be used by remailer clients to choose a chain of remailers to
+ use.
+ .
+ Furthermore, Echolot collects configuration parameters and keys of
+ remailers and offers the collected information in a format readable by
+ remailer clients. This helps reduce the administration effort required
+ to use or host remailers.
diff --git a/trunk/debian/copyright b/trunk/debian/copyright
new file mode 100644
index 0000000..8aac572
--- /dev/null
+++ b/trunk/debian/copyright
@@ -0,0 +1,24 @@
+This package was debianized by Peter Palfrader <weasel@debian.org> on
+Mon, 22 Jul 2002 10:30:40 +0200.
+
+It was downloaded from http://www.palfrader.org/echolot/
+
+Upstream Authors: Peter Palfrader
+
+Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+
+Echolot 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
+St, Fifth Floor, Boston, MA 02110-1301 USA
+
+On Debian systems you can find a copy of the GNU General Public License in
+/usr/share/common-licenses/GPL.
diff --git a/trunk/debian/echolot.default b/trunk/debian/echolot.default
new file mode 100644
index 0000000..c0c1bc4
--- /dev/null
+++ b/trunk/debian/echolot.default
@@ -0,0 +1,12 @@
+# Defaults for echolot initscript
+# sourced by /etc/init.d/echolot
+# installed at /etc/default/echolot
+
+#
+# This is a POSIX shell fragment
+#
+
+VERBOSE=0
+
+# Only enable this after you've read /usr/share/doc/echolot/README.Debian
+RUN_ECHOLOT=0
diff --git a/trunk/debian/echolot.dirs b/trunk/debian/echolot.dirs
new file mode 100644
index 0000000..05c67a0
--- /dev/null
+++ b/trunk/debian/echolot.dirs
@@ -0,0 +1,7 @@
+usr/bin
+usr/share/perl5
+var/lib/echolot
+var/run/echolot
+var/log/echolot
+etc/echolot
+etc/default
diff --git a/trunk/debian/echolot.docs b/trunk/debian/echolot.docs
new file mode 100644
index 0000000..19e2cdd
--- /dev/null
+++ b/trunk/debian/echolot.docs
@@ -0,0 +1,3 @@
+README
+TODO
+UPGRADE
diff --git a/trunk/debian/echolot.init b/trunk/debian/echolot.init
new file mode 100755
index 0000000..9768148
--- /dev/null
+++ b/trunk/debian/echolot.init
@@ -0,0 +1,174 @@
+#!/bin/sh
+#
+# pingcntl: echolot control + wrapper
+# Written by admin@arancio.net, peter@palfrader.org
+
+set -e
+
+VERBOSE=0
+
+# You probably don't want to mess with stuff below this line
+################################################################
+
+PIDFILE=/var/run/echolot/pingd.pid
+CHECKULIMIT=1
+CHECKUID=1
+USER=echolot
+GROUP=echolot
+DAEMON=/usr/bin/pingd
+DESC="Echolot Ping Daemon"
+NAME="pingd"
+test -f $DAEMON || exit 0
+
+PATH=/bin:/usr/bin:/sbin:/usr/sbin
+export PATH
+
+# Reads config file (will override defaults above)
+[ -r /etc/default/echolot ] && . /etc/default/echolot
+
+
+wait_for_deaddaemon () {
+ PID=$1
+ sleep 3
+ if test -n "$PID"
+ then
+ if kill -0 $PID 2>/dev/null
+ then
+ echo -n "Waiting for pid $PID ."
+ cnt=0
+ while kill -0 $PID 2>/dev/null
+ do
+ cnt=`expr $cnt + 1`
+ if [ $cnt -gt 30 ]
+ then
+ echo " Failed.. "
+ exit 1
+ fi
+ sleep 2
+ echo -n "."
+ done
+ rm -f $PIDFILE
+ else
+ rm -f $PIDFILE
+ fi
+ fi
+}
+
+
+# Check for evil ulimits
+if [ "$CHECKULIMIT" -gt "0" ]; then
+ FDs=`ulimit -n`
+ HFDs=`ulimit -H -n`
+ if [ "$FDs" -lt "512" ]; then
+ if [ "$HFDs" -lt "512" ]; then
+ echo "Hardlimit for open File Descriptors is less than 512." >&2
+ echo "Please consider raising it." >&2
+ if [ "$FDs" -lt "$HFDs" ]; then
+ echo "Raising it to $HFDs" >&2
+ ulimit -n $HFDs
+ fi
+ else
+ if [ "$HFDs" -lt "1024" ]; then
+ FDs=$HFDs
+ else
+ FDs=1024
+ fi
+ echo "Softlimit for open File Descriptors is less than 512." >&2
+ echo "Raising it to $FDs" >&2
+ ulimit -n $FDs
+ fi
+ fi
+fi
+
+
+# set VERBOSE
+if [ $VERBOSE -gt 0 ]; then
+ VERBOSE="--verbose"
+else
+ VERBOSE=""
+fi
+
+
+case $1 in
+
+start)
+ if [ -f $PIDFILE ] ; then
+ PID=`cat $PIDFILE 2>/dev/null` || true
+ if kill -0 $PID 2>/dev/null
+ then
+ echo "$DESC already running."
+ exit 0
+ else
+ echo -n "Removing stale pid file: "
+ rm -f $PIDFILE
+ echo "$PIDFILE."
+ fi
+ fi
+ if [ $RUN_ECHOLOT -gt 0 ]; then
+ echo -n "Starting $DESC: "
+ start-stop-daemon \
+ --start \
+ --quiet \
+ --pidfile $PIDFILE \
+ --chuid $USER:$GROUP \
+ --exec $DAEMON -- --detach $VERBOSE --process --quiet start
+ echo "$NAME."
+ else
+ echo "Not starting $DESC: disabled in configuration"
+ fi
+ ;;
+
+stop)
+ echo -n "Stopping $DESC: "
+ PID=`cat $PIDFILE 2>/dev/null` || true
+ start-stop-daemon \
+ --stop \
+ --quiet \
+ --oknodo \
+ --pidfile $PIDFILE \
+ --user $USER
+ wait_for_deaddaemon $PID
+ echo "$NAME."
+ ;;
+
+reload|force-reload|restart)
+ PID=`cat $PIDFILE 2>/dev/null` || true
+ $0 stop
+ wait_for_deaddaemon $PID
+ $0 start
+ ;;
+process|add|delete|set|setremailercaps|deleteremailercaps|getkeyconf|sendpings|sendchainpings|buildstats|buildkeys|buildthesaurus|buildfromlines|dumpconf|summary|enable|disable)
+ # Check for right User
+ if [ "$CHECKUID" -gt "0" ]; then
+ CUID=`id -u`
+ CUIDNAME=`id -nu`
+ if [ "$CUIDNAME" = "$USER" ]; then
+ "$DAEMON" "$@"
+ elif [ "$CUID" = "0" ]; then
+ command="$DAEMON"
+ while [ "$#" -gt 0 ]; do
+ command="$command \"$1\""
+ shift
+ done
+ su "$USER" -c "$command"
+ else
+ echo "You are neither $USER nor root. Aborting." >&2
+ exit 1;
+ fi
+ fi
+
+ echo "Running $DESC: $NAME $1..."
+ echo "done."
+ ;;
+*)
+ echo "Usage: $0 (start|stop|reload|force-reload|restart)" >&2
+ echo " $0 <COMMAND> [parameters]" >&2
+ echo "See the pingd(1) manual page for valid commands" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
+
+# vim:set ts=2:
+# vim:set shiftwidth=2:
diff --git a/trunk/debian/echolot.links b/trunk/debian/echolot.links
new file mode 100644
index 0000000..1a0bac4
--- /dev/null
+++ b/trunk/debian/echolot.links
@@ -0,0 +1 @@
+/etc/echolot/templates /var/lib/echolot/templates
diff --git a/trunk/debian/echolot.logrotate b/trunk/debian/echolot.logrotate
new file mode 100644
index 0000000..dd105d6
--- /dev/null
+++ b/trunk/debian/echolot.logrotate
@@ -0,0 +1,11 @@
+/var/log/echolot/echolot.log {
+ weekly
+ rotate 5
+ compress
+ missingok
+ notifempty
+ postrotate
+ /etc/init.d/echolot process > /dev/null
+ endscript
+ create 640 echolot adm
+}
diff --git a/trunk/debian/echolot.manpages b/trunk/debian/echolot.manpages
new file mode 100644
index 0000000..f1c1209
--- /dev/null
+++ b/trunk/debian/echolot.manpages
@@ -0,0 +1,2 @@
+doc/pingd.1
+doc/pingd.conf.5
diff --git a/trunk/debian/echolot.postinst b/trunk/debian/echolot.postinst
new file mode 100755
index 0000000..4004475
--- /dev/null
+++ b/trunk/debian/echolot.postinst
@@ -0,0 +1,41 @@
+#!/bin/sh -e
+
+# postinst for Echolot
+# remove old debconf stuff
+if [ "$1" = "configure" ] && [ -e /usr/share/debconf/confmodule ]; then
+ if dpkg --compare-versions "$2" lt "2.1.7" ; then
+ . /usr/share/debconf/confmodule
+ db_purge
+ db_stop
+ fi
+fi
+
+# Make sure the echolot user exists
+adduser --quiet \
+ --quiet \
+ --system \
+ --disabled-password \
+ --shell /bin/bash \
+ --home /var/lib/echolot \
+ --no-create-home \
+ --group \
+ --gecos "Echolot Pinger" \
+ echolot
+
+# Give the echolot user write permissions to /var/log/echolot/echolot.log
+touch /var/log/echolot/echolot.log
+chown echolot:adm /var/log/echolot/echolot.log
+chmod 640 /var/log/echolot/echolot.log
+# and /var/lib/echolot
+if ( ! dpkg-statoverride --list /var/lib/echolot > /dev/null ); then
+ dpkg-statoverride --update --add root echolot 02775 /var/lib/echolot
+fi
+# and /var/run/echolot
+if ( ! dpkg-statoverride --list /var/run/echolot > /dev/null ); then
+ dpkg-statoverride --update --add root echolot 02770 /var/run/echolot
+fi
+
+#DEBHELPER#
+
+# vim:set ts=4:
+# vim:set shiftwidth=4:
diff --git a/trunk/debian/echolot.postrm b/trunk/debian/echolot.postrm
new file mode 100755
index 0000000..ea9b021
--- /dev/null
+++ b/trunk/debian/echolot.postrm
@@ -0,0 +1,28 @@
+#!/bin/sh -e
+
+# postrm for Echolot
+
+case "$1" in
+ purge)
+ rm -rf /var/lib/echolot/*
+ rmdir /etc/echolot/templates 2>/dev/null || true
+ rmdir /etc/echolot 2>/dev/null || true
+ dpkg-statoverride --remove /var/run/echolot >/dev/null 2>&1 || true
+ dpkg-statoverride --remove /var/lib/echolot >/dev/null 2>&1 || true
+ ;;
+ remove|upgrade|deconfigure)
+ ;;
+ failed-upgrade)
+ ;;
+ abort-upgrade)
+ ;;
+ *)
+ echo "unknown argument --> $1" >&2
+ exit 0
+ ;;
+esac
+
+#DEBHELPER#
+
+# vim:set ts=2:
+# vim:set shiftwidth=2:
diff --git a/trunk/debian/pingd.conf b/trunk/debian/pingd.conf
new file mode 100644
index 0000000..d1c5b89
--- /dev/null
+++ b/trunk/debian/pingd.conf
@@ -0,0 +1,25 @@
+# vim:set syntax=perl:
+
+# see man pingd.conf(5) for a list of all available configuration options.
+
+$CONFIG = {
+ 'separate_rlists' => 1,
+ 'combined_list' => 1,
+ };
+
+$CONFIG->{'mailin' } = '/var/mail/echolot';
+
+# You probably want to modify these:
+$CONFIG->{'my_localpart' } = 'echolot';
+$CONFIG->{'my_domain' } = 'pinger.example.org';
+$CONFIG->{'operator_address' } = 'abuse@pinger.example.org';
+$CONFIG->{'sitename' } = 'example';
+#$CONFIG->{'recipient_delimiter' } = '+';
+$CONFIG->{'recipient_delimiter' } = '';
+
+
+$CONFIG->{'mixmaster'} = 'mixmaster';
+$CONFIG->{'homedir' } = '/var/lib/echolot';
+$CONFIG->{'pidfile' } = '/var/run/echolot/pingd.pid';
+$CONFIG->{'logfile' } = '/var/log/echolot/echolot.log';
+1;
diff --git a/trunk/debian/rules b/trunk/debian/rules
new file mode 100755
index 0000000..64796b1
--- /dev/null
+++ b/trunk/debian/rules
@@ -0,0 +1,77 @@
+#!/usr/bin/make -f
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 to 1999 by Joey Hess.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+# This is the debhelper compatibility version to use.
+export DH_COMPAT=4
+
+version=`grep 'VERSION =' pingd | sed -e "s/.* '//" -e "s/'.*//"`
+REMAILERS=`tr '\n' ' ' < debian/remailers`
+
+ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS)))
+ CFLAGS += -g
+endif
+ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS)))
+ INSTALL_PROGRAM += -s
+endif
+
+build: build-stamp
+build-stamp:
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+
+ rm -f build-stamp
+
+ dh_clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+
+ tar c Echolot | tar xv -C $(CURDIR)/debian/echolot/usr/share/perl5/
+ tar c --exclude LICENSE templates | tar xv -C $(CURDIR)/debian/echolot/etc/echolot
+ install -m 755 pingd $(CURDIR)/debian/echolot/usr/bin/
+ install -m 644 debian/pingd.conf $(CURDIR)/debian/echolot/etc/echolot
+ install -m 644 debian/echolot.default $(CURDIR)/debian/echolot/etc/default/echolot
+
+
+binary-indep: build install
+ dh_testdir
+ dh_testroot
+ dh_installdebconf
+ dh_installdocs
+ dh_installmenu
+ dh_installlogrotate
+ dh_installman
+ dh_installchangelogs NEWS
+ dh_installinit
+ dh_link
+ dh_strip
+ dh_compress
+ dh_fixperms
+ dh_installdeb
+ dh_shlibdeps
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+binary-arch:
+# We have nothing to do by default.
+
+binary: binary-indep binary-arch
+
+update-remailers:
+ dh_testdir
+ wget -O - http://stats.melontraffickers.com/mlist2.txt | grep '@' | sed -e 's/.*<//' -e 's/>.*//' > debian/remailers
+
+debian/remailers: update-remailers
+
+.PHONY: build clean binary-indep binary-arch binary install configure remailers
diff --git a/trunk/doc/methodology b/trunk/doc/methodology
new file mode 100644
index 0000000..e64cb1f
--- /dev/null
+++ b/trunk/doc/methodology
@@ -0,0 +1,60 @@
+
+In order to test remailer A's reliability, Echolot sends an encrypted
+ping through a 1-hop chain every two hours. Pings are not sent strictly
+at 02:00, 04:00 etc, but at 2h*n + f(A, date) % 2h. ( f() is a function
+of the remailer name and the date of the current day (basically md5))
+
+We record the timestamp each outgoing ping, and we record the time it
+took to return for incoming pings.
+
+The reliability of a node is the result of received/sent, with the
+following weighting applied:
+
+ weight := w1 * w2;
+
+ w1 is a function of a ping's age:
+ age: 1 2 3 4 5 6 7 8 9 10 11 12 [days]
+ weight 0.5 1.0 1.0 1.0 1.0 0.9 0.8 0.5 0.3 0.2 0.2 0.1
+
+ age is how long ago the ping was sent. So if a ping was sent 23
+ hour ago, it weighs 0.5, if it was sent 2 days ago, its weight is 1.
+ Approaching 12 days, the weight approaches 0.0.
+
+ w2 also considers this node's pings' latencies over the last 12 days:
+
+ for pings that already returned, w2 is 1.0.
+ otherwise:
+ Let mod_age := (now - sent - 15m) * 0.8
+ w2 is the fraction of pings returned within mod_age seconds.
+
+ Example:
+ Assume a ping was sent 2 hours ago. mod_age is 84 minutes. If 100%
+ of this node's pings were faster than 84 minutes, then w2 = 1. If
+ only 30% were received within 84 minutes of sending out the ping,
+ then w2 is 0.3, If no ping was ever faster than 84 minutes, then w2
+ is 0.
+
+
+The reported latency is the median of all received pings of the last 12
+days.
+
+
+
+Chain pings are done in a similar fashion: We ping two-hop chains A, B.
+Each chain is pinged once a week (with a similar offset function as
+above).
+
+"Interesting chains" are pinged more often - daily.
+
+We report chains as broken if
+ - we sent at least 3 pings
+ AND
+ - received/sent <= rel(A) * rel(B) * 0.3.
+
+rel(X) is remailer X's reliability in single-hop pings.
+
+We define interesting chains as chains
+ - where we sent less than 3 pings, without getting any back.
+ OR
+ - where received/sent <= rel(A) * rel(B) * 0.3 (i.e. the chain is
+ reported broken)
diff --git a/trunk/doc/pingd.conf.pod b/trunk/doc/pingd.conf.pod
new file mode 100644
index 0000000..ae3d5ac
--- /dev/null
+++ b/trunk/doc/pingd.conf.pod
@@ -0,0 +1,910 @@
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+=pod
+
+=head1 NAME
+
+pingd.conf - configuration file for the Echolot ping daemon
+
+=head1 DESCRIPTION
+
+The file B<pingd.conf> sets configuration parameters for Echolot pingd(1).
+It is a Perl script that gets eval()ed from within pingd. It has to set the
+values in the $CONFIG hash.
+
+=cut
+
+=head1 OPTIONS
+
+=head2 REQUIRED OPTIONS
+
+=over
+
+=item B<sitename>
+
+A short name for your site/pinger. It is used in the statistics produced.
+
+ Default: none
+ Example: 'sitename' => 'testsite',
+
+=item B<my_localpart>
+
+The local part of the pinger's email address.
+
+In C<pinger@remailer.example.com> the localpart is C<pinger>.
+
+ Default: none
+ Example: 'my_localpart' => 'pinger',
+
+=item B<my_domain>
+
+The domain part (FQDN) of the pinger's email address.
+
+In C<pinger@remailer.example.com> the domain part is C<remailer.example.com>.
+
+ Default: none
+ Example: 'my_domain' => 'remailer.example.com',
+
+=item B<operator_address>
+
+The email address of the human operator that runs this pinger.
+
+ Default: none
+ Example: 'operator_address' => 'remop@example.org',
+
+It is used in several templates.
+
+=back
+
+
+
+=head2 SYSTEM SPECIFIC OPTIONS
+
+=over
+
+=item B<recipient_delimiter>
+
+The B<recipient_delimiter> parameter specifies the separator between user names
+and address extensions (user+foo).
+
+If it is an empty string Echolot does not make use of user defined mailboxes
+but rather encodes the message type etc in a Comment/Realname part of an
+address.
+
+The use of recipient_delimiter is strongly recommended if your MTA setup
+supports it.
+
+ Default: 'recipient_delimiter' => '+',
+ Example: 'recipient_delimiter' => '-',
+ 'recipient_delimiter' => '',
+
+Example addresses:
+
+with + as a recipient delimiter:
+ pinger+conf.1=1035540778=1dd23d97@example.org
+
+without recipient delimiter:
+ pinger@example.org (conf.2=1035541597=3baa2ae5)
+
+=item B<dev_random>
+
+Where to read strong random data from - currently used only for generating our
+secret.
+
+ Default: 'dev_random' => '/dev/random',
+
+=item B<dev_urandom>
+
+Where to read weak random data from - currently used only for
+garbage generation.
+
+ Default: 'dev_urandom' => '/dev/urandom',
+
+=item B<sendmail>
+
+Path to the sendmail binary. It is expected to accept the C<-f> and C<-t>
+parameters.
+
+ Default: 'sendmail' => '/usr/sbin/sendmail',
+ Example: 'sendmail' => '/usr/lib/sendmail',
+
+=back
+
+
+
+=head2 MAGIC NUMBERS
+
+=over
+
+=item B<hash_len> [integer]
+
+Echolot uses email addresses of the form C<foo+some_data=MAC@domain>. MAC
+is a Message Authentication Code used to verify that the address
+was actually generated by this pinger using a secret which is set
+from random data the first time you run B<pingd>. Echolot uses MD5
+as the MAC hash function.
+
+B<hash_len> is the number of characters to include in the email address.
+
+ Default: 'hash_len' => 8,
+ Example: 'hash_len' => 4,
+
+=item B<seconds_per_day> [integer]
+
+The length of one character in reliability and latency stats. One
+character usually stands for exactly one day (hence the name of this
+config option). Changing it in production use is probably a bad idea
+but shortening it might come in handy during debugging.
+
+ Default: 'seconds_per_day' => 24*60*60,
+
+=item B<stats_days> [integer]
+
+How many days (or whatever you configured seconds_per_day to really be)
+to have in the stats. This is 12 days.
+
+ Default: 'stats_days' => 12,
+
+=back
+
+
+
+=head2 NEW REMAILERS
+
+=over
+
+=item B<fetch_new> [bool]
+
+Query new remailers for remailer-xxx replies by default.
+
+ Default: 'fetch_new' => 1,
+ Example: 'fetch_new' => 0,
+
+=item B<ping_new> [bool]
+
+Ping new remailers by default.
+
+ Default: 'ping_new' => 1,
+ Example: 'ping_new' => 0,
+
+=item B<show_new> [bool]
+
+Show new remailers in public stats by default.
+
+ Default: 'show_new' => 1,
+ Example: 'show_new' => 0,
+
+=back
+
+
+
+=head2 STATISTICS GENERATION
+
+=over
+
+=item B<separate_rlists> [bool]
+
+Also build separate rlists with data from only DSA pings, only RSA pings and
+only unencrypted pings.
+
+ Default: 'separate_realists' => 0,
+ Example: 'separate_rlists' => 1,
+
+=item B<combined_list> [bool]
+
+Build a combined list of all different stats too. While there is no
+standard format it is nice to read for the human eye.
+
+ Default: 'combined_list' => 0,
+ Example: 'combined_list' => 1,
+
+=item B<thesaurus> [bool]
+
+Collect Thesaurus data and build Thesaurus Index.
+
+ Default: 'thesaurus' => 1,
+ Example: 'thesaurus' => 0,
+
+=item B<fromlines> [bool]
+
+Build a summary of default From: header lines and list
+remailers which allow overriding them.
+
+ Default: 'fromlines' => 1,
+ Example: 'fromlines' => 0,
+
+=item B<stats_sort_by_latency>
+
+In the statistics output remailers are sorted by reliability as the primary key.
+The secondary key is usually nickname. If you prefer to sort by latency rather
+than nick set this to 1 (-1 if you want to reverse the order).
+
+ Default: 'stats_sort_by_latency' => 0,
+ Example: 'stats_sort_by_latency' => 1,
+
+
+=back
+
+
+
+=head2 TIMERS AND COUNTERS
+
+=over
+
+=item B<processmail> [seconds]
+
+How often to process incoming email.
+
+ Default: 'processmail' => 60, # every minute
+ Example: 'processmail' => 5*60, # every 5 minutes
+
+=item B<buildstats> [seconds]
+
+How often to build mlist etc.
+
+ Default: 'buildstats' => 5*60, # every 5 minutes
+ Example: 'buildstats' => 60*60, # hourly
+
+=item B<chainping_update> [seconds]
+
+When building stats and we have chain pinging enabled
+(see B<do_chainpings>), how often to rebuild chain stats.
+This can be a CPU intensive task therefore it's not updated
+every time stats are built.
+
+ Default: 'chainping_update' => 4*60*60, # chain stats should never
+ # be older than 4 hours
+
+=item B<buildkeys> [seconds]
+
+How often to build keyrings.
+
+ Default: 'buildkeys' => 8*60*60, # every 8 hours
+ Example: 'buildkeys' => 24*60*60, # daily
+
+=item B<buildthesaurus> [seconds]
+
+How often to update thesaurus index page.
+
+ Default: 'buildthesaurus' => 60*60, # hourly
+ Example: 'buildthesaurus' => 24*60*60, # daily
+
+=item B<commitprospectives> [seconds]
+
+How often to check for prospective new remailer addresses and
+commit them to the list of remailers.
+
+ Default: 'commitprospectives' => 8*60*60, # every 8 hours
+ Example: 'commitprospectives' => 24*60*60, # daily
+
+=item B<expire> [seconds]
+
+How often to expire old keys, pings and remailers
+
+ Default: 'expire' => 24*60*60, # daily
+ Example: 'expire' => 8*60*60, # every 8 hours
+
+=item B<getkeyconf_interval> [seconds]
+
+=item B<getkeyconf_every_nth_time> [integer]
+
+How often to query remailers for new keys and configuration data
+(remailer-xxx). Some requests are sent every B<getkeyconf_interval>
+seconds. The same request to the same remailer is sent only every
+B<getkeyconf_every_nth_time> time.
+
+ Default: 'getkeyconf_interval' => 5*60, # send out requests
+ # every 5 minutes
+ 'getkeyconf_every_nth_time' => 24*60/5, # send out the same
+ # request to the same
+ # remailer once a day
+ Example: 'getkeyconf_interval' => 10*60,
+ 'getkeyconf_every_nth_time' => 2*24*60/10, # new request every
+ # other day
+
+=item B<check_resurrection> [seconds]
+
+How often to check assumed dead remailers for resurrection.
+
+ Default: 'check_resurrection' => 7*24*60*60, # weekly
+ Example: 'check_resurrection' => 14*24*60*60, # every other week
+
+=item B<pinger_interval> [seconds]
+
+=item B<ping_every_nth_time> [integer]
+
+How often to send pings. Pings are sent every B<pinger_interval> seconds. The
+same remailer is pinged every B<ping_every_nth_time> time pings are sent (This
+means the same remailer is pinged every B<pinger_interval> *
+B<ping_every_nth_time> seconds). It is done this way in order to avoid
+spikes.
+
+ Default: 'pinger_interval' => 5*60, # send out pings every 5 minutes
+ 'ping_every_nth_time' => 24, # send out pings to the same remailer every 24 calls, i.e. every 2 hours
+ Example: 'pinger_interval' => 60, # send out pings every minute
+ 'ping_every_nth_time' => 60, # send out pings to the same remailer every 60 calls, i.e. every hour
+
+=item B<chainpinger_interval> [seconds]
+
+=item B<chainping_every_nth_time> [integer]
+
+=item B<chainping_ic_every_nth_time> [integer]
+
+How often to send chain pings. Chain-Pings are sent every
+B<chainpinger_interval> seconds. The same chain is pinged every
+B<chainping_every_nth_time> time chain-pings are sent. Chains in
+I<Intensive Care> (ic), that are chains that are either known or
+believed to be bad or are not tested enough yet (see
+B<chainping_minsample>), should be tested more often: They are checked
+every B<chainping_ic_every_nth_time> time chain-pings are sent.
+
+ Default: 'chainpinger_interval' => 5*60, # send out pings every 5 minutes
+ 'chainping_every_nth_time' => 2016, # send out pings to the same chain every 2016 calls, i.e. week
+ 'chainping_ic_every_nth_time' => 288, # send out pings to broken or unknown chains every 288 calls, i.e. daily
+
+=item B<addresses_default_ttl> [integer]
+
+How many times to request remailer-xxx from a remailer (done every
+B<getkeyconf> seconds, daily per default) without a reply before it is assumed
+dead.
+
+ Default: 'addresses_default_ttl' => 5, # getkeyconf seconds (days if getkeyconf is 24*60*60, the default)
+ Example: 'addresses_default_ttl' => 7,
+
+=item B<check_resurrection_ttl> [integer]
+
+How many times to request remailer-xxx from an assumed dead remailer (done every
+B<check_resurrection> seconds, weekly per default) without a reply before it is
+really considered dead.
+
+ Default: 'check_resurrection_ttl' => 8, # check_resurrection seconds (weeks if check_resurrection is 7*24*60*60, the default)
+ Example: 'check_resurrection_ttl' => 4,
+
+=item B<prospective_addresses_ttl> [seconds]
+
+How long to keep information about a prospective address in the database.
+Addresses that are not committed to the list of remailer addresses are
+expired after this time.
+
+ Default: 'prospective_addresses_ttl' => 5*24*60*60, # 5 days
+ Example: 'prospective_addresses_ttl' =>14*24*60*60, # 2 weeks
+
+=item B<reliable_auto_add_min> [integer]
+
+How many different remailers need to list an address in a remailer-conf
+reply to get it committed to the list of remailer addresses.
+
+ Default: 'reliable_auto_add_min' => 6,
+ Example: 'reliable_auto_add_min' => 3,
+
+=item B<expire_keys> [seconds]
+
+After how long to expire received keys if they were not updated by remailer-key replies.
+
+ Default: 'expire_keys' => 5*24*60*60, # 5 days
+ Example: 'expire_keys' => 7*24*60*60, # 1 week
+
+=item B<expire_confs> [seconds]
+
+After how long to expire received remailer-conf replies.
+
+ Default: 'expire_confs' => 5*24*60*60, # 5 days
+ Example: 'expire_confs' => 7*24*60*60, # 1 week
+
+=item B<expire_pings> [seconds]
+
+After how long to expire pings. 12 is the value of choice
+because that is the time frame the statistics show. You should
+not make this smaller than 12 days.
+
+ Default: 'expire_pings' => 12*24*60*60, # 12 days
+
+=item B<expire_chainpings> [seconds]
+
+After how long to expire chain pings. This should probably
+be set to the same as B<chainping_period>.
+
+ Default: 'expire_chainpings' => 12*24*60*60, # 12 days
+
+=item B<expire_thesaurus> [seconds]
+
+After how long to expire files in the thesaurus directory.
+
+ Default: 'expire_thesaurus' => 21*24*60*60, # 2 weeks
+ Example: 'expire_thesaurus' => 7*24*60*60, # 1 week
+
+=item B<expire_fromlines> [seconds]
+
+After how long to expire header From: lines.
+
+ Default: 'expire_fromlines' => 5*24*60*60, # 5 days
+ Example: 'expire_fromlines' => 7*24*60*60, # 1 week
+
+=item B<cleanup_tmpdir> [seconds]
+
+How often to clean old files from the temp directory.
+ Default: 'cleanup_tmpdir' => 24*60*60, # daily
+
+=item B<metadata_backup> [seconds]
+
+How often to make backups of metadata and rotate them. If gzip is set, backups
+are compressed.
+
+ Default: 'metadata_backup' => 8*60*60, # 8 hours
+ Example: 'metadata_backup' => 24*60*60, # daily
+
+=item B<metadata_backup_count> [integer]
+
+How many backups of metadata to keep.
+
+ Default: 'metadata_backup_count' => 32, # keep the last 32 backups
+ Example: 'metadata_backup_count' => 4, # keep 4 rotations
+
+=item B<summary> [seconds]
+
+How often to print a status summary to the log.
+
+ Default: 'summary' => 24*60*60, # daily
+ Default: 'summary' => 12*60*60, # twice a day
+
+=back
+
+
+=head2 DIRECTORIES AND FILES AND RELATED OPTIONS
+
+=over
+
+=item B<homedir>
+
+The base directory of the Echolot installation. All other filenames and
+directory names are local to this directory. B<pingd> changes into this
+directory upon startup.
+
+ Default: The directory in which pingd is.
+ Example: 'homedir' => '/home/pinger/echolot',
+
+=item B<mailin>
+
+The Maildir directory or Mbox which is searched for new messages.
+
+ Default: 'mailin' => 'mail',
+ Example: 'mailin' => '/var/mail/echolot',
+
+=item B<mailerrordir>
+
+The Maildir directory where messages are put that could not be parsed.
+
+ Default: 'mailerrordir' => 'mail-errors',
+
+=item B<save_errormails> [bool]
+
+Whether to keep error messages at all
+
+ Default: 'save_errormails' => 0,
+ Example: 'save_errormails' => 1,
+
+=item B<resultdir>
+
+The directory where statistics and keyrings are put.
+
+ Default: 'resultdir' => 'results',
+
+=item B<thesaurusdir>
+
+The directory where Thesaurus data is put.
+
+ Default: 'thesaurusdir' => 'results/thesaurus',
+
+=item B<thesaurusindexfile>
+
+The Thesaurus index file.
+
+ Default: 'thesaurusindexfile' => 'results/thesaurus/index',
+
+=item B<fromlinesindexfile>
+
+The From Lines index file.
+
+ Default: 'fromlinesindexfile' => 'results/from',
+
+=item B<private_resultdir>
+
+The directory where private stats and keyrings are put (Remailers that have
+show set to false are shown here too).
+
+ Default: 'private_resultdir' => 'results.private',
+
+=item B<indexfilebasename>
+
+The file to write the index.html to (relative to the result directory).
+
+ Default: 'indexfilebasename' => 'echolot',
+ Example: 'indexfilebasename' => 'index',
+
+=item B<gnupghome>
+
+The directory which is used as temporary GnuPG home for all keyring and
+encryption/decryption actions.
+
+ Default: 'gnupghome' => 'gnupghome',
+
+=item B<gnupg>
+
+Name of the GnuPG executable. If it is not in your PATH make sure to
+include path information.
+
+If B<gnupg> is an empty string, the C<GnuPG::Interface> default (usually B<gpg>)
+is used.
+
+ Default: 'gnupg' => '',
+ Example: 'gnupg' => '/home/pinger/bin/myGnuPG',
+
+=item B<gzip>
+
+Name of the gzip executable. If it is not in your PATH make sure to
+include path information.
+
+ Default: 'gzip' => 'gzip',
+
+=item B<mixhome>
+
+The directory which is used as temporary Mixmaster home for all keyring and
+encryption/decryption actions.
+
+ Default: 'mixhome' => 'mixhome',
+ Example: 'mixhome' => '/home/pinger/Mix',
+
+=item B<mixmaster>
+
+Name of the mixmaster executable. If it is not in your PATH make sure to
+include path information.
+
+ Default: 'mixmaster' => 'mix',
+ Example: 'mixmaster' => '/home/pinger/Mix/mix',
+
+=item B<tmpdir>
+
+General purpose temp directory. Make sure it is not shared with other
+applications.
+
+ Default: 'tmpdir' => 'tmp',
+
+=item B<commands_file>
+
+A file where commands to the daemon process are stored. The client
+puts commands (like add a new remailer) in it and then sends a HUP
+to the daemon process which reads and empties the file.
+
+ Default: 'commands_file' => 'commands.txt',
+
+=item B<pidfile>
+
+The daemon's PID file. The daemon's Process ID is stored in this file.
+As long as it exists pingd refuses to start up in daemon mode.
+
+ Default: 'pidfile' => 'pingd.pid',
+
+=item B<broken1>
+
+File listing broken type I remailer chains. If it does not exist, the part is
+skipped in generated stats. Otherwise its content is copied in verbatim.
+
+ Default: 'broken1' => 'broken1.txt',
+ Example content:
+ (havenco cmeclax)
+ (frog3 nycrem)
+
+=item B<broken2>
+
+File listing broken type II remailer chains. If it does not exist, the part is
+skipped in generated stats. Otherwise its content is copied in verbatim.
+
+ Default: 'broken2' => 'broken2.txt',
+ Example content:
+ (freedom lcs)
+ (* xganon)
+
+=item B<sameop>
+
+File listing remailers that have the same operator or share a machine or other
+important infrastructure. If it does not exist, the part is skipped in
+generated stats. Otherwise its content is copied in verbatim.
+
+ Default: 'sameop' => 'sameop.txt',
+ Example content:
+ (xganon2 xganon)
+ (cracker redneck)
+
+=back
+
+
+=head2 LOGGING
+
+=over
+
+=item B<logfile>
+
+File to write logs to. This file is reopened on SIGHUP.
+
+ Default: 'logfile' => 'pingd.log',
+ Example: 'logfile' => '/var/log/echolot/pingd.log',
+
+=item B<loglevel>
+
+Minimum severity of messages to include in log file. Possible values are
+B<trace>, B<debug>, B<info>, B<notice>, B<warning>, B<error>, B<critical>, B<alert>, and
+B<emergency>.
+
+ Default: 'loglevel' => 'info',
+ Example: 'loglevel' => 'debug',
+
+=back
+
+
+=head2 MISCELLANEOUS
+
+=over
+
+=item B<write_meta_files> [bool]
+
+Whether to write meta files for each created file. These files include
+meta information for http servers and http clients like the date when
+a specific page expires.
+
+ Default: 'write_meta_files' => 1,
+
+=item B<meta_extension>
+
+The extension that such metafiles (see above) should have.
+
+ Default: 'meta_extension' => '.meta',
+
+=item B<random_garbage> [integer]
+
+Pings usually are quite short. Some 100 bytes are sufficient to relay
+all the information that is required. To make them not stand out that
+obviously, pings are padded using random garbage of random length.
+
+B<random_garbage> is the top limit for the amount of bytes to add. The
+actual number is randomly generated and uniformly distributed over
+[0, B<random_garbage>]
+
+ Default: 'random_garbage' => '8192',
+
+=back
+
+=head2 CHAIN PINGING
+
+=over
+
+=item B<do_chainpings> [bool]
+
+Whether or not to do chain pings. Chain pings test all chains
+of two remailers and come up with a list of broken chains.
+This produces a non-trivial amount of traffic.
+
+ Default: 'do_chainpings' => 1,
+
+=item B<show_chainpings> [bool]
+
+Show the results of our chainpinging in public stats.
+
+ Default: 'show_chainpings' => 1,
+
+=item B<chainping_fudge>
+
+What proportion of the expected replies derived from one-hop stats
+must return before a chain is not declared broken.
+
+ Default: 'chainping_fudge' => 0.3, # if less than 0.3 * rel1 * rel2 make it, the chain is really broken
+
+=item B<chainping_grace>
+
+The factor of time in addition to the guessed latency
+derived from one-hop stats before a chain ping is considered lost
+
+ Default: 'chainping_grace' => 1.5, # don't count pings sent no longer than 1.5 * (lat1 + lat2) ago
+
+=item B<chainping_period> [seconds]
+
+What time frame is taken into account when calculating chain stats.
+This should probably be smaller than B<expire_chainpings>.
+
+ Default: 'chainping_period' => 12*24*60*60, # 12 days
+
+=item B<chainping_minsample> [seconds]
+
+Have at least as many sent (and not within grace) chain pings before
+declaring a chain broken.
+
+ Default: 'chainping_minsample' => 3, # have at least sent 3 pings before judging any chain
+
+=item B<chainping_allbad_factor>
+
+How many chains C<(A x)> must be bad before C<(A *)> is listed.
+The value is given as a proportion of all available remailers.
+
+ Default: chainping_allbad_factor => 0.5, # at least 50% of possible chains (A x) need to fail for (A *) to be listed in broken chains
+
+=back
+
+
+=head2 PINGING TYPES
+
+=over
+
+=item B<do_pings>
+
+B<do_pings> determines which ping types are sent.
+It is a hash that has the following keys:
+
+=over
+
+=item B<cpunk-dsa>
+
+Send out CPunk pings to CPunk remailers with their DSA key.
+
+=item B<cpunk-rsa>
+
+Send out CPunk pings to CPunk remailers with their RSA key.
+
+=item B<cpunk-clear>
+
+Send out unencrypted pings to CPunk remailers that don't have pgponly
+in their capsstring.
+
+=item B<mix>
+
+Pings mixmaster remailers.
+
+=back
+
+ Default: 'do_pings' => {
+ 'cpunk-dsa' => 1,
+ 'cpunk-rsa' => 1,
+ 'cpunk-clear' => 1,
+ 'mix' => 1
+ },
+
+=item B<which_chainpings>
+
+B<which_chainpings> controls some aspects of chain pinging.
+It's a hash over chaintypes - currently B<mix> and B<cpunk>.
+Each entry is a reference to an array which specifies the
+preference for key types in that chaintype.
+
+ Default: which_chainpings => {
+ 'cpunk' => [ qw{cpunk-dsa cpunk-rsa cpunk-clear} ],
+ 'mix' => [ qw{mix} ]
+ },
+
+This means that in the case of cpunk chain pings we prefer
+using cpunk-dsa over cpunk-rsa which in turn we prefer
+to cpunk-clear. For mix there's only mix.
+
+=item B<pings_weight>
+
+Not all pings have the same influence on the average reliability
+calcluated. Very new pings don't count fully since there is some
+margin of error. Similarly very old pings are not that interesting
+either.
+
+By default days 1 to 4 count fully (with weight 1), the older they
+are the less they count.
+
+ Default: pings_weight => [ qw{0.5 1.0 1.0 1.0 1.0 0.9 0.8 0.5 0.3 0.2 0.2 0.1 } ],
+
+=back
+
+=head2 TEMPLATES
+
+=over
+
+=item B<templates>
+
+The template files are used to generate the HTML version of all Echolot output.
+It is a hash of hashes which each have following keys:
+B<thesaurusindexfile>,
+B<mlist>,
+B<mlist2>,
+B<rlist>,
+B<rlist-rsa>,
+B<rlist-dsa>,
+B<rlist-clear>,
+B<rlist2>,
+B<rlist2-rsa>,
+B<rlist2-dsa>,
+B<rlist2-clear>, and
+B<clist>.
+
+The outer hash keys are for language selection.
+
+ Default: 'templates' => {
+ 'default' => {
+ 'thesaurusindexfile' => 'templates/thesaurusindex.html',
+ 'mlist' => 'templates/mlist.html',
+ 'mlist2' => 'templates/mlist2.html',
+ 'rlist' => 'templates/rlist.html',
+ 'rlist-rsa' => 'templates/rlist-rsa.html',
+ 'rlist-dsa' => 'templates/rlist-dsa.html',
+ 'rlist-clear' => 'templates/rlist-clear.html',
+ 'rlist2' => 'templates/rlist2.html',
+ 'rlist2-rsa' => 'templates/rlist2-rsa.html',
+ 'rlist2-dsa' => 'templates/rlist2-dsa.html',
+ 'rlist2-clear' => 'templates/rlist2-clear.html',
+ 'clist' => 'templates/clist.html',
+ },
+ 'de' => {
+ 'thesaurusindexfile' => 'templates/thesaurusindex.de.html',
+ ....
+ },
+ 'pl' => {
+ 'thesaurusindexfile' => 'templates/thesaurusindex.pl.html',
+ ....
+ }
+ };
+
+=item B<echolot_css>
+
+Location of the CSS file. This is copied to resultdir/echolot.css.
+
+ Default: 'echolot_css' => 'templates/echolot.css',
+
+=back
+
+
+
+=head2 STRINGS
+
+=over
+
+=item B<remailerxxxtext>
+
+The text to send along with remailer-xxx queries.
+The template variables address and operator_address are substituted for their
+real values.
+
+ Default: 'remailerxxxtext' => "Hello,\n".
+ "\n".
+ "This message requests remailer configuration data. The pinging software thinks\n".
+ "<TMPL_VAR NAME=\"address\"> is a remailer. Either it has been told so by the\n".
+ "maintainer of the pinger or it found the address in a remailer-conf or\n".
+ "remailer-key reply of some other remailer.\n".
+ "\n".
+ "If this is _not_ a remailer, you can tell this pinger that and it will stop\n".
+ "sending you those requests immediately (otherwise it will try a few more times).\n".
+ "Just reply and make sure the following is the first line of your message:\n".
+ " not a remailer\n".
+ "\n".
+ "If you want to talk to a human please mail <TMPL_VAR NAME=\"operator_address\">.\n",
+
+=back
+
+=head1 AUTHOR
+
+Peter Palfrader E<lt>peter@palfrader.orgE<gt>
+
+=head1 BUGS
+
+Please report them at E<lt>URL:http://alioth.debian.org/projects/echolot/<gt>
+
+=cut
diff --git a/trunk/pingd b/trunk/pingd
new file mode 100755
index 0000000..39681b6
--- /dev/null
+++ b/trunk/pingd
@@ -0,0 +1,854 @@
+#!/usr/bin/perl -w
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+$| = 1;
+
+=pod
+
+=head1 NAME
+
+pingd - echolot ping daemon
+
+=head1 SYNOPSIS
+
+=over
+
+=item B<pingd> B<start>
+
+=item B<pingd> B<stop>
+
+=item B<pingd> B<process>
+
+=item B<pingd> B<add> I<address> [I<address> ...]
+
+=item B<pingd> B<delete> I<address> [I<address> ...]
+
+=item B<pingd> B<disable> I<address> [I<address> ...]
+
+=item B<pingd> B<enable> I<address> [I<address> ...]
+
+=item B<pingd> B<set> option=value [option=value..] I<address> [I<address> ...]
+
+=item B<pingd> B<setremailercaps> I<capsstring>
+
+=item B<pingd> B<deleteremailercaps> I<address>
+
+=item B<pingd> B<getkeyconf> [I<address> [I<address> ...]]
+
+=item B<pingd> B<sendpings> [I<address> [I<address> ...]]
+
+=item B<pingd> B<sendchainpings> I<address>B<:>I<address> [I<address>B<:>I<address> ...]
+
+=item B<pingd> B<buildstats>
+
+=item B<pingd> B<buildkeys>
+
+=item B<pingd> B<buildthesaurus>
+
+=item B<pingd> B<buildfromlines>
+
+=item B<pingd> B<summary>
+
+=item B<pingd> B<dumpconf>
+
+=back
+
+=head1 DESCRIPTION
+
+pingd is the heart of echolot. Echolot is a pinger for anonymous remailers.
+
+A Pinger in the context of anonymous remailers is a program that regularily
+sends messages through remailers to check their reliability. It then calculates
+reliability statistics which are used by remailer clients to choose the chain
+of remailers to use.
+
+Additionally it collects configuration parameters and keys of all remailers and
+offers them in a format readable by remailer clients.
+
+When called without parameters pingd schedules tasks like sending pings,
+processing incoming mail and requesting remailer-xxx data and runs them at
+configurable intervalls.
+
+=head1 COMMANDS
+
+=over
+
+=item B<start>
+
+Start the ping daemon.
+
+=item B<stop>
+
+Send the running pingd process a SIGTERM.
+
+=item B<process>
+
+Sends a HUP signal to the daemon which instructs it to process the commands.
+
+For other effects of sending the HUP Signal see the SIGNALS section below.
+
+=item B<add> I<address> [I<address> ...]
+
+Add I<address> to the list of remailers to query for
+keys and confs.
+
+=item B<delete> I<address> [I<address> ...]
+
+Delete I<address> from the list of remailers to query for
+keys and confs. Delete all statistics and keys for that remailer.
+
+Note that echolot will add back this remailer if it learns of it from other
+remailers again. If you do not want that, use the disable command.
+
+=item B<disable> I<address> [I<address> ...]
+
+Shorthand for B<set showit=off> B<pingit=off> B<fetch=off>. This makes echolot
+completely ignore that remailer, until you enable it again.
+
+=item B<enable> I<address> [I<address> ...]
+
+Shorthand for B<set showit=on> B<pingit=on> B<fetch=on>.
+
+=item B<set> option=value [option=value..] I<address> [I<address> ...]
+
+Possible options and values:
+
+=over
+
+=item B<showit=>{B<on>,B<off>}
+
+Set B<showit> (show remailer in mlist, rlist etc.) for remailer I<address> to
+either B<on> or B<off>.
+
+=item B<pingit=>{B<on>,B<off>}
+
+Set B<pingit> (send out pings to that remailer) for remailer I<address> to
+either B<on> or B<off>.
+
+=item B<fetch=>{B<on>,B<off>}
+
+Set B<fetch> (fetch remailer-key and remailer-conf) for remailer I<address> to
+either B<on> or B<off>.
+
+=back
+
+=item B<setremailercaps> I<capsstring>
+
+Some remailers (Mixmaster V2 - currently lcs and passthru2) don't return a
+useable remailer-conf message. For such remailers you need to set it manually.
+
+For instance:
+
+ ./pingd setremailercaps '$remailer{"passthru2"} = "<mixer@immd1.informatik.uni-erlangen.de> mix middle";'
+ ./pingd setremailercaps '$remailer{"lcs"} = "<mix@anon.lcs.mit.edu> mix klen1000";'
+
+=item B<deleteremailercaps> I<address>
+
+Delete remailer-conf data for I<address>. The config data will be reset from
+the next valid remailer-conf reply by the remailer.
+
+=item B<getkeyconf> [I<address> [I<address> ...]]
+
+Send a command to immediatly request keys and configuration from remailers.
+If no addresses are given requests will be sent to all remailers.
+
+=item B<sendpings> [I<address> [I<address> ...]]
+
+Send a command to immediatly send pings to the given remailers.
+If no addresses are given requests will be sent to all remailers.
+
+=item B<sendchainpings> I<address>B<:>I<address> [I<address>B<:>I<address> ...]
+
+Send a command to immediatly send chain pings to the given chains.
+A chain is two remailer addresses seperated by a colon.
+
+=item B<buildstats>
+
+Send a command to immediatly rebuild stats.
+
+=item B<buildkeys>
+
+Send a command to immediatly rebuild the keyrings.
+
+=item B<buildthesaurus>
+
+Send a command to immediatly rebuild the Thesaurus.
+
+=item B<buildfromlines>
+
+Send a command to immediatly rebuild the From Header lines page.
+
+=item B<summary>
+
+Print a status summary of all known addresses to the log (level notice).
+
+=item B<dumpconf>
+
+Dumps the current configuration to standard output.
+
+=back
+
+=head1 OPTIONS
+
+=over
+
+=item B<--basedir>
+
+The home directory to which everything else is relative. See the BASE
+DIRECTORY section below.
+
+=item B<--verbose>
+
+Verbose mode. Causes B<pingd> to print debugging messages about its progress.
+
+=item B<--quiet>
+
+Quiet mode. Be even quieter than normal.
+
+=item B<--help>
+
+Print a short help message and exit sucessfully.
+
+=item B<--version>
+
+Print version number and exit sucessfully.
+
+=item B<--nohup>
+
+Usefull only with the B<add>, B<set>, B<setremailercaps>,
+B<deleteremailercaps>, B<getkeyconf>, B<sendpings>, B<sendchainpings>,
+B<buildstats>, B<buildkeys>, B<buildthesaurus>, B<buildfromlines>,
+or B<summary> command.
+
+Don't send a HUP signal to the daemon which instructs it to process the
+commands after adding the command to the task list.
+
+By default such a signal is sent.
+
+=item B<--process>
+
+Usefull only with the B<start> command.
+
+Read and process the commands file on startup.
+
+=item B<--detach>
+
+Usefull only with the B<start> command.
+
+Tell B<pingd> to detach.
+
+=back
+
+=head1 BASE DIRECTORY
+
+The home directory to which everything else is relative.
+
+Basedir defaults to whatever directory the B<pingd> binary is located. It can
+be overridden by the B<ECHOLOT_HOME> environment variable which in turn is
+weaker than the B<--basedir> setting.
+
+This directory is then used to locate the configuration file B<pingd.conf> (see
+FILES below).
+
+The B<homedir> setting in B<pingd.conf> finally sets the base directory.
+
+=head1 FILES
+
+The configuration file is searched in these places in this order:
+
+=over
+
+=item the file pointed to by the B<ECHOLOT_CONF> environment variable
+
+=item <basedir>/pingd.conf
+
+=item $HOME/echolot/pingd.conf
+
+=item $HOME/pingd.conf
+
+=item $HOME/.pingd.conf
+
+=item /etc/echolot/pingd.conf
+
+=item /etc/pingd.conf
+
+=back
+
+=head1 ENVIRONMENT
+
+=over
+
+=item ECHOLOT_CONF echolot config file (see section FILES)
+
+=item ECHOLOT_HOME echolot base directory (see section BASE DIRECTORY)
+
+=back
+
+=head1 SIGNALS
+
+On B<SIGINT>, B<SIGQUIT>, and B<SIGTERM> B<pingd> will schedule a shutdown
+for as soon as the current actions are finished or immediatly if no actions are
+currently being processed. It will then write all metadata and pingdata to
+disk and close all files cleanly before exiting.
+
+On B<SIGHUP> <pingd> will execute any pending commands from the commands file
+(B<commands.txt> by default). It also closes and reopens the file 'output'
+which is used for stdout and stderr when the daemon is running detached.
+This can be used if you want to rotate that file.
+
+=head1 AUTHOR
+
+Peter Palfrader E<lt>peter@palfrader.orgE<gt>
+
+=head1 BUGS
+
+Please report them at E<lt>URL:http://alioth.debian.org/projects/echolot/<gt>
+
+=cut
+
+use strict;
+use FindBin qw{ $Bin };
+use lib ( $Bin, "$Bin/lib" );
+use Getopt::Long;
+use English;
+use Carp;
+use Echolot::Config;
+use Echolot::Globals;
+use Echolot::Storage::File;
+use Echolot::Scheduler;
+use Echolot::Conf;
+use Echolot::Mailin;
+use Echolot::Pinger;
+use Echolot::Chain;
+use Echolot::Stats;
+use Echolot::Commands;
+use Echolot::Thesaurus;
+use Echolot::Fromlines;
+use Echolot::Report;
+use Echolot::Log;
+use POSIX qw(setsid);
+
+delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
+
+
+my $VERSION = '2.1.8';
+
+
+my $redirected_stdio = 0;
+
+sub setSigHandlers() {
+ $SIG{'HUP'} = sub {
+ Echolot::Log::info("Got SIGHUP. scheduling readcommands.");
+ Echolot::Globals::get()->{'scheduler'}->schedule('readcommands', 0, time() );
+ if ($redirected_stdio) {
+ close STDOUT;
+ close STDERR;
+ my $logfile = Echolot::Config::get()->{'logfile'};
+ open (STDOUT, ">>$logfile") or die ("Cannot open '$logfile' as STDOUT\n");
+ open (STDERR, ">&STDOUT") or die ("Cannot dup STDOUT as STDERR\n");
+ };
+ Echolot::Log::reopen();
+ };
+ $SIG{'INT'} = sub {
+ Echolot::Log::info("Got SIGINT. scheduling exit.");
+ Echolot::Globals::get()->{'scheduler'}->schedule('exit', 0, 0 );
+ };
+ $SIG{'QUIT'} = sub {
+ Echolot::Log::info("Got SIGQUIT. scheduling exit.");
+ Echolot::Globals::get()->{'scheduler'}->schedule('exit', 0, 0 );
+ };
+ $SIG{'TERM'} = sub {
+ Echolot::Log::info("Got SIGTERM. scheduling exit.");
+ Echolot::Globals::get()->{'scheduler'}->schedule('exit', 0, 0 );
+ };
+ $SIG{'PIPE'} = sub {
+ Echolot::Log::error("Got SIGPIPE");
+ };
+};
+
+
+
+sub commit_prospective_address() {
+ Echolot::Globals::get()->{'storage'}->commit_prospective_address();
+};
+sub expire() {
+ Echolot::Globals::get()->{'storage'}->expire();
+};
+sub metadata_backup() {
+ Echolot::Globals::get()->{'storage'}->metadata_backup();
+};
+
+
+
+
+
+sub command_adddelete(@) {
+ my $command = shift @_;
+ my @argv = @_;
+
+ die ("command_adddelete requires command\n") unless defined $command;
+ die ("add requires argument <address>\n") unless scalar @argv;
+ my @addresses;
+ for my $address (@argv) {
+ die ("argument <address> is not a valid email address\n") unless ($address =~ /^[a-zA-Z0-9+._-]+\@[a-zA-Z0-9+.-]+$/ );
+ push @addresses, $address;
+ };
+ for my $address (@addresses) {
+ Echolot::Commands::addCommand("$command $address");
+ };
+};
+
+sub command_set(@) {
+ my @argv = @_;
+
+ my @settings;
+ while (scalar @argv && $argv[0] =~ /^(show(?:it)?|ping(?:it)?|fetch(?:it)?)=(on|off)$/) {
+ my $name = $1;
+ my $value = $2;
+
+ $name = 'showit' if ($name eq 'show');
+ $name = 'pingit' if ($name eq 'ping');
+ $name = 'fetch' if ($name eq 'fetchit');
+
+ push @settings, "$name=$value";
+ shift @argv;
+ };
+
+ my @addresses;
+ for my $address (@argv) {
+ die ("argument $address is not a valid email address\n") unless ($address =~ /^[a-zA-Z0-9+._-]+\@[a-zA-Z0-9+.-]+$/ );
+ push @addresses, $address;
+ };
+
+ for my $address (@argv) {
+ for my $setting (@settings) {
+ Echolot::Commands::addCommand("set $address $setting");
+ };
+ };
+};
+
+sub command_enabledisable (@) {
+ my @argv = @_;
+
+ my $command = shift @argv;
+ my $value;
+ if ($command eq 'enable') {
+ $value='on';
+ } elsif ($command eq 'disable') {
+ $value='off';
+ } else {
+ die ("argument $command is not enable or disable. Shouldn't even be here.\n");
+ };
+ my @cmd = ("showit=$value", "pingit=$value", "fetch=$value");
+ push @cmd, @argv;
+ command_set(@cmd);
+}
+
+
+sub command_setremailercaps(@) {
+ my @argv = @_;
+
+ my @caps;
+ for my $caps (@argv) {
+ my ($remailer_nick, $remailer_address) = ($caps =~ /^\s* \$remailer{"(.*)"} \s*=\s* "<(.*@.*)>.*"; \s*$/ix);
+ die ("caps '$caps' is not a valid remailer caps line\n") unless (defined $remailer_nick && defined $remailer_address);
+ push @caps, {
+ address => $remailer_address,
+ caps => $caps };
+ };
+ for my $caps (@caps) {
+ Echolot::Commands::addCommand("setremailercaps ".$caps->{'address'}." ".$caps->{'caps'});
+ };
+};
+
+sub command_deleteremailercaps(@) {
+ my @argv = @_;
+
+ my @addresses;
+ for my $address (@argv) {
+ die ("argument $address is not a valid email address\n") unless ($address =~ /^[a-zA-Z0-9+._-]+\@[a-zA-Z0-9+.-]+$/ );
+ push @addresses, $address;
+ };
+
+ for my $address (@addresses) {
+ Echolot::Commands::addCommand("deleteremailercaps $address");
+ };
+};
+
+sub command_getkeyconf(@) {
+ my @argv = @_;
+
+ my @addresses;
+ for my $address (@argv) {
+ die ("argument $address is not a valid email address\n") unless ($address =~ /^[a-zA-Z0-9+._-]+\@[a-zA-Z0-9+.-]+$/ );
+ push @addresses, $address;
+ };
+
+ push @addresses, 'all' unless (scalar @addresses);
+
+ for my $address (@addresses) {
+ Echolot::Commands::addCommand("getkeyconf $address");
+ };
+};
+
+sub command_sendpings(@) {
+ my @argv = @_;
+
+ my @addresses;
+ for my $address (@argv) {
+ die ("argument $address is not a valid email address\n") unless ($address =~ /^[a-zA-Z0-9+._-]+\@[a-zA-Z0-9+.-]+$/ );
+ push @addresses, $address;
+ };
+
+ push @addresses, 'all' unless (scalar @addresses);
+
+ for my $address (@addresses) {
+ Echolot::Commands::addCommand("sendpings $address");
+ };
+};
+
+sub command_sendchainpings(@) {
+ my @argv = @_;
+
+ my @args;
+ for my $arg (@argv) {
+ my ($addr1, $addr2) = split /:/, $arg;
+ die ("Argument $arg is not in the form address1:address2") unless defined $addr1 && defined $addr2;
+ die ("Address 1 in $arg ('$addr1') is not a valid email address\n") unless ($addr1 =~ /^[a-zA-Z0-9+._-]+\@[a-zA-Z0-9+.-]+$/ );
+ die ("Address 2 in $arg ('$addr2') is not a valid email address\n") unless ($addr2 =~ /^[a-zA-Z0-9+._-]+\@[a-zA-Z0-9+.-]+$/ );
+ push @args, [ $addr1, $addr2 ];
+ };
+
+ for my $arg (@args) {
+ Echolot::Commands::addCommand("sendchainpings $arg->[0] $arg->[1]");
+ };
+};
+
+
+sub pid_exists($) {
+ my ($remove_stale) = @_;
+
+ my $pidfile = Echolot::Config::get()->{'pidfile'};
+ if (! $remove_stale) {
+ return (-e $pidfile);
+ } else {
+ if (!-e $pidfile) {
+ return 0;
+ };
+
+ open (PIDFILE, $pidfile) or
+ die ("Cannot open pidfile '$pidfile': $!.\n");
+ my $line = <PIDFILE>;
+ close PIDFILE;
+
+ my ($pid, $host, $time) = $line =~ /^(\d+) \s+ (\S+) \s+ (\d+) \s* $/x or
+ die ("Cannot parse pidfile '$pidfile' line '$line'.\n");
+
+ (Echolot::Globals::get()->{'hostname'} eq $host) or
+ die ("Pidfile exists and is from another host.\n");
+ ($host ne 'unknown') or
+ die ("Pidfile exists and hostname is unknown.\n");
+ ($time < time()) or
+ die ("Pidfile exists and timestamp is in the future.\n");
+ my $sent = kill 0, $pid;
+ ($sent == 0) or
+ die ("Pidfile exists and process $pid running.\n");
+ warn ("Removing stale pidfile.\n");
+ unlink ($pidfile) or
+ die ("Removing stale pidfile $pidfile failed.\n");
+ return 0;
+ }
+
+
+};
+
+sub daemon_run($) {
+ my ($process) = @_;
+
+ Echolot::Log::logdie("Pidfile '".Echolot::Config::get()->{'pidfile'}."' exists\n")
+ if pid_exists(0);
+ open (PIDFILE, '>'.Echolot::Config::get()->{'pidfile'}) or
+ Echolot::Log::logdie("Cannot open pidfile '".Echolot::Config::get()->{'pidfile'}."': $!");
+ print PIDFILE "$PROCESS_ID ".Echolot::Globals::get()->{'hostname'}." ".time()."\n";
+ close PIDFILE;
+
+ Echolot::Globals::initStorage();
+ setSigHandlers();
+
+ Echolot::Globals::get()->{'scheduler'} = new Echolot::Scheduler;
+ my $scheduler = Echolot::Globals::get()->{'scheduler'};
+ $scheduler->add('exit' , -1 , 0, 0, 'exit' );
+ $scheduler->add('readcommands' , -1 , 0, 1, \&Echolot::Commands::processCommands );
+
+ $scheduler->add('processmail' , Echolot::Config::get()->{'processmail'} , 0, 1, \&Echolot::Mailin::process );
+ $scheduler->add('ping' , Echolot::Config::get()->{'pinger_interval'} , 0, 0, \&Echolot::Pinger::send_pings );
+ $scheduler->add('chainping' , Echolot::Config::get()->{'chainpinger_interval'} , 0, 0, \&Echolot::Chain::send_pings );
+ $scheduler->add('buildstats' , Echolot::Config::get()->{'buildstats'} , 0, 1, \&Echolot::Stats::build_stats );
+ $scheduler->add('buildkeys' , Echolot::Config::get()->{'buildkeys'} , 0, 1, \&Echolot::Stats::build_keys );
+ $scheduler->add('buildthesaurus' , Echolot::Config::get()->{'buildthesaurus'} , 0, 1, \&Echolot::Thesaurus::build_thesaurus );
+ $scheduler->add('buildfromlines' , Echolot::Config::get()->{'buildfromlines'} , 0, 1, \&Echolot::Fromlines::build_fromlines );
+
+ $scheduler->add('metadata_backup' , Echolot::Config::get()->{'metadata_backup'} , 0, 1, \&metadata_backup );
+ $scheduler->add('commitprospectives' , Echolot::Config::get()->{'commitprospectives'} , 0, 1, \&commit_prospective_address );
+ $scheduler->add('expire' , Echolot::Config::get()->{'expire'} , 0, 1, \&expire );
+ $scheduler->add('getkeyconf' , Echolot::Config::get()->{'getkeyconf_interval'} , 0, 0, \&Echolot::Conf::send_requests );
+ $scheduler->add('check_resurrection' , Echolot::Config::get()->{'check_resurrection'} , 0, 0, \&Echolot::Conf::check_resurrection );
+ $scheduler->add('cleanup_tmp' , Echolot::Config::get()->{'cleanup_tmpdir'} , 0, 1, \&Echolot::Tools::cleanup_tmp );
+
+ $scheduler->add('summary' , Echolot::Config::get()->{'summary'} , 0, 1, \&Echolot::Report::print_summary );
+
+ Echolot::Globals::get()->{'scheduler'}->schedule('readcommands', 0, time() )
+ if ($process);
+
+ $scheduler->run();
+
+ Echolot::Globals::get()->{'storage'}->commit();
+ Echolot::Globals::get()->{'storage'}->finish();
+
+ unlink (Echolot::Config::get()->{'pidfile'}) or
+ Echolot::Log::warn ("Cannot unlink pidfile ".Echolot::Config::get()->{'pidfile'}.".");
+};
+
+sub send_sig($) {
+ my ($sig) = @_;
+
+ die ("Pidfile '".Echolot::Config::get()->{'pidfile'}."' does not exist\n")
+ unless pid_exists(0);
+ open (PIDFILE, '<'.Echolot::Config::get()->{'pidfile'}) or
+ confess ("Cannot open pidfile '".Echolot::Config::get()->{'pidfile'}."': $!\n");
+ my $line = <PIDFILE>;
+ close PIDFILE;
+
+ my ($pid, $host, $time) = $line =~ /^(\d+) \s+ (\S+) \s+ (\d+) \s* $/x or
+ confess ("Cannot parse pidfile '$line'\n");
+ my $sent = kill $sig, $pid;
+ ($sent == 1) or
+ confess ("Did not send signal $sig to exactly one process but $sent. (pidfile reads $line)\n");
+};
+
+sub daemon_hup() {
+ send_sig(1);
+};
+
+sub daemon_stop() {
+ send_sig(15);
+};
+
+sub make_dirs() {
+ for my $dir (
+ Echolot::Config::get()->{'resultdir'},
+ Echolot::Config::get()->{'thesaurusdir'},
+ ) {
+ if ( ! -d $dir ) {
+ mkdir ($dir, 0755) or
+ confess ("Cannot create directory $dir: $!\n");
+ };
+ };
+ my @dirs = (
+ Echolot::Config::get()->{'private_resultdir'},
+ Echolot::Config::get()->{'gnupghome'},
+ Echolot::Config::get()->{'mixhome'},
+ Echolot::Config::get()->{'tmpdir'},
+ Echolot::Config::get()->{'storage'}->{'File'}->{'basedir'},
+ Echolot::Config::get()->{'mailerrordir'},
+ Echolot::Config::get()->{'mailerrordir'}.'/cur',
+ Echolot::Config::get()->{'mailerrordir'}.'/tmp',
+ Echolot::Config::get()->{'mailerrordir'}.'/new');
+ push @dirs, (
+ Echolot::Config::get()->{'mailin'}.'/cur',
+ Echolot::Config::get()->{'mailin'}.'/tmp',
+ Echolot::Config::get()->{'mailin'}.'/new' )
+ if (-d Echolot::Config::get()->{'mailin'});
+
+ for my $dir (@dirs) {
+ if ( ! -d $dir ) {
+ mkdir ($dir, 0700) or
+ confess ("Cannot create directory $dir: $!\n");
+ };
+ };
+};
+
+sub hup_if_wanted($) {
+ my ($nohup) = @_;
+ if (!$nohup && pid_exists(0)) {
+ daemon_hup()
+ } else {
+ print "Don't forget to run $PROGRAM_NAME process.\n";
+ };
+};
+
+
+
+
+
+
+
+
+($EUID == 0) and
+ die("$PROGRAM_NAME should not be run as root.\n");
+
+my $params = { basedir => $Bin };
+$params->{'basedir'} = $ENV{'ECHOLOT_HOME'} if (defined $ENV{'ECHOLOT_HOME'});
+
+Getopt::Long::config('bundling');
+if (!GetOptions (
+ 'help' => \$params->{'help'},
+ 'version' => \$params->{'version'},
+ 'verbose+' => \$params->{'verbose'},
+ 'nohup' => \$params->{'nohup'},
+ 'detach' => \$params->{'detach'},
+ 'process' => \$params->{'process'},
+ 'basedir' => \$params->{'basedir'},
+ 'quiet' => \$params->{'quiet'},
+ )) {
+ die ("$PROGRAM_NAME: Usage: $PROGRAM_NAME [-fwhv]\n");
+};
+if ($params->{'help'}) {
+ print ("Usage: $PROGRAM_NAME [options] command\n");
+ print ("See man pingd or perldoc pingd for more info.\n");
+ print ("echolot $VERSION - (c) 2002, 2003, 2004 Peter Palfrader <peter\@palfrader.org>\n");
+ print ("http://alioth.debian.org/projects/echolot/\n");
+ print ("\n");
+ print ("Commands:\n");
+ print (" start starts echolot pingd\n");
+ print (" signals pingd to ... \n");
+ print (" stop ... shutdown\n");
+ print (" process ... reopen outfile and process commands\n");
+ print (" add ... add a remailer address\n");
+ print (" delete ... delete a remailer address\n");
+ print (" disable ... disable a remailer\n");
+ print (" enable ... enable a remailer\n");
+ print (" set ... set remailer options\n");
+ print (" setremailercaps ... set remailer capabilities manually\n");
+ print (" deleteremailercaps ... delete remailer capabilities manually\n");
+ print (" getkeyconf ... request remailer-xxx data immediatly\n");
+ print (" sendpings ... request immediate sending of pings\n");
+ print (" sendchainpings ... request immediate sending of chainpings\n");
+ print (" buildstats ... build remailer stats immediatly\n");
+ print (" buildkeys ... buid keyrings immediatly\n");
+ print (" buildthesaurus ... build thesaurus immediatly\n");
+ print (" buildfromlines ... build fromlines immediatly\n");
+ print (" summary ... print status summary to log\n");
+ print (" dumpconf ... dump configuration\n");
+ exit 0;
+};
+if ($params->{'version'}) {
+ print ("echolot $VERSION\n");
+ print ("(c) 2002, 2003, 2004 Peter Palfrader <peter\@palfrader.org>\n");
+ print ("http://alioth.debian.org/projects/echolot/\n");
+ exit 0;
+};
+$params->{'quiet'} = undef if ($params->{'verbose'});
+
+my $COMMAND = shift @ARGV;
+die ("command required\n") unless defined $COMMAND;
+
+
+Echolot::Config::init( $params );
+chdir( Echolot::Config::get()->{'homedir'} );
+Echolot::Globals::init( version => $VERSION);
+
+
+if ($COMMAND eq 'add' || $COMMAND eq 'delete') {
+ command_adddelete($COMMAND, @ARGV);
+ hup_if_wanted($params->{'nohup'});
+} elsif ($COMMAND eq 'set') {
+ command_set(@ARGV);
+ hup_if_wanted($params->{'nohup'});
+} elsif ($COMMAND eq 'enable' || $COMMAND eq 'disable') {
+ command_enabledisable($COMMAND, @ARGV);
+ hup_if_wanted($params->{'nohup'});
+} elsif ($COMMAND eq 'deleteremailercaps') {
+ command_deleteremailercaps(@ARGV);
+ hup_if_wanted($params->{'nohup'});
+} elsif ($COMMAND eq 'setremailercaps') {
+ command_setremailercaps(@ARGV);
+ hup_if_wanted($params->{'nohup'});
+} elsif ($COMMAND eq 'getkeyconf') {
+ command_getkeyconf(@ARGV);
+ hup_if_wanted($params->{'nohup'});
+} elsif ($COMMAND eq 'sendpings') {
+ command_sendpings(@ARGV);
+ hup_if_wanted($params->{'nohup'});
+} elsif ($COMMAND eq 'sendchainpings') {
+ command_sendchainpings(@ARGV);
+ hup_if_wanted($params->{'nohup'});
+} elsif ($COMMAND eq 'buildstats') {
+ Echolot::Commands::addCommand("buildstats");
+ hup_if_wanted($params->{'nohup'});
+} elsif ($COMMAND eq 'buildkeys') {
+ Echolot::Commands::addCommand("buildkeys");
+ hup_if_wanted($params->{'nohup'});
+} elsif ($COMMAND eq 'buildthesaurus') {
+ Echolot::Commands::addCommand("buildthesaurus");
+ hup_if_wanted($params->{'nohup'});
+} elsif ($COMMAND eq 'buildfromlines') {
+ Echolot::Commands::addCommand("buildfromlines");
+ hup_if_wanted($params->{'nohup'});
+} elsif ($COMMAND eq 'summary') {
+ Echolot::Commands::addCommand("summary");
+ hup_if_wanted($params->{'nohup'});
+} elsif ($COMMAND eq 'process') {
+ daemon_hup();
+} elsif ($COMMAND eq 'stop') {
+ daemon_stop();
+} elsif ($COMMAND eq 'start') {
+ # FIXME: remove stale pid files
+ die ("Pidfile '".Echolot::Config::get()->{'pidfile'}."' exists\n")
+ if pid_exists(1);
+ Echolot::Log::init();
+ make_dirs();
+ Echolot::Config::check_binaries();
+ if ($params->{'detach'}) {
+ print "Detaching.\n" unless ($params->{'quiet'});
+ Echolot::Log::debug("Detaching.");
+ exit(0) if (fork());
+ POSIX::setsid();
+ exit(0) if (fork());
+ my $logfile = Echolot::Config::get()->{'logfile'};
+ open (STDOUT, ">>$logfile") or die ("Cannot open '$logfile' as STDOUT\n");
+ open (STDERR, ">&STDOUT") or die ("Cannot dup STDOUT as STDERR\n");
+ open (STDIN , "</dev/null") or die ("Cannot open /dev/null as STDIN\n");
+ $redirected_stdio = 1;
+ Echolot::Log::info "Starting up.";
+ daemon_run( $params->{'process'} );
+ Echolot::Log::info "Shutdown complete.";
+ } else {
+ daemon_run( $params->{'process'} );
+ };
+} elsif ($COMMAND eq 'dumpconf') {
+ Echolot::Config::dump();
+} elsif ($COMMAND eq 'convert') {
+ Echolot::Globals::initStorage();
+ setSigHandlers();
+
+ Echolot::Globals::get()->{'storage'}->convert();
+
+ Echolot::Globals::get()->{'storage'}->commit();
+ Echolot::Globals::get()->{'storage'}->finish();
+} else {
+ die ("Command $COMMAND unknown");
+};
+
+exit 0;
+
+# vim: set ts=4 shiftwidth=4:
diff --git a/trunk/pingd.conf.sample b/trunk/pingd.conf.sample
new file mode 100644
index 0000000..338a2e0
--- /dev/null
+++ b/trunk/pingd.conf.sample
@@ -0,0 +1,72 @@
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+# see man pingd.conf(5) for a list of all available configuration options.
+# There are a lot more than those listed in this sample.
+
+$CONFIG = {
+ # A short name for your site/pinger. Is used in the statistics produced.
+ 'sitename' => 'unconfigured',
+
+ # The local part of the pinger's email address
+ # In "pinger@remailer.example.com" the localpart is "pinger".
+ 'my_localpart' => 'pinger',
+
+ # The domain part (FQDN) of the pinger's email address.
+ # In "pinger@remailer.example.com" the domain part is "remailer.example.com".
+ 'my_domain' => 'remailer.example.com',
+
+ # The email address of the human operator that runs this pinger.
+ 'operator_address' => 'remop@example.org',
+
+ # Name of the mixmaster executable. If it is not in your PATH make
+ # sure to include path information.
+ #'mixmaster' => '/home/pinger/Mix/mix',
+
+ # The recipient_delimiter parameter specifies the separator between user names
+ # and address extensions (user+foo).
+ #
+ # If it is an empty string Echolot does not make use of user defined mailboxes
+ # but rather encodes the message type et al in a Comment/Realname part of an
+ # address.
+ #
+ # The use of recipient_delimiter is strongly recommended if your MTA setup
+ # supports it.
+ 'recipient_delimiter' => '+',
+ #'recipient_delimiter' => '',
+
+ # Also build separate rlists with data from only DSA pings, only RSA
+ # pings and only unencrypted pings.
+ 'separate_rlists' => 0,
+
+ # Build a combined list of all different stats too. While this is no
+ # standard format it is nice to read for a human eye.
+ 'combined_list' => 0,
+
+ # Log file and level (valid levels are debug, info, notice, warning, error,
+ # critical, alert, emergency)
+ #'logfile' => 'pingd.log',
+ #'loglevel' => 'info',
+
+ };
+1;
+
+# vim:set syntax=perl:
diff --git a/trunk/templates/LICENSE b/trunk/templates/LICENSE
new file mode 100644
index 0000000..1d4790a
--- /dev/null
+++ b/trunk/templates/LICENSE
@@ -0,0 +1,39 @@
+$Id$
+
+The files in this directory are part of Echolot.
+
+In order to keep the templates short, the license statement is in this file.
+It applies to the files in this directory, namley:
+ * clist.html
+ * echolot.css
+ * echolot.html
+ * fromlinesindex.html
+ * mlist.html
+ * mlist2.html
+ * rlist-clear.html
+ * rlist-dsa.html
+ * rlist-rsa.html
+ * rlist.html
+ * rlist2-clear.html
+ * rlist2-dsa.html
+ * rlist2-rsa.html
+ * rlist2.html
+ * thesaurusindex.html
+ * v1legend.html
+ * v2legend.html
+
+Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+
+Echolot 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 St, Fifth Floor, Boston, MA 02110-1301 USA
diff --git a/trunk/templates/clist.html b/trunk/templates/clist.html
new file mode 100644
index 0000000..26277d7
--- /dev/null
+++ b/trunk/templates/clist.html
@@ -0,0 +1,72 @@
+<html>
+<head>
+ <title>Remailers (combined list) [<TMPL_VAR NAME="SITE_NAME">]</title>
+ <META HTTP-EQUIV="Expires" CONTENT="<TMPL_VAR NAME="EXPIRES">">
+ <link href="echolot.css" rel="stylesheet" type="text/css">
+ <!-- $Id$ -->
+</head>
+<body>
+<h1>Remailer Reliability Stats [<TMPL_VAR NAME="SITE_NAME">]</h1>
+
+<p>
+<a HREF="./">Up</a> <a href="clist.txt">Plaintext version</a>
+
+<p>
+Available Stats:
+<table border=1>
+<tr>
+<TMPL_IF NAME="separate_rlist">
+ <th colspan=4>Cypherpunk (Type I)</th>
+<TMPL_ELSE>
+ <th>Cypherpunk (Type I)</th>
+</TMPL_IF>
+ <th>Mixmaster (Type II)</th>
+<TMPL_IF NAME="combined_list">
+ <th>Combined</th>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist.html">v1</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist-rsa.html">(rsa)</a></td>
+ <td><a href="rlist-dsa.html">(dsa)</a></td>
+ <td><a href="rlist-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist.html">v1</a></td>
+<TMPL_IF NAME="combined_list">
+ <td rowspan=2><strong>V2</strong></td>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist2.html">v2</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist2-rsa.html">(rsa)</a></td>
+ <td><a href="rlist2-dsa.html">(dsa)</a></td>
+ <td><a href="rlist2-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist2.html">v2</a></td>
+</tr>
+</table>
+
+<h2>Remailers (combined list)</h2>
+
+<p>
+This is an automatically generated list of remailer reliability
+statistics.
+Please see the <a href="#legend">Legend</a> below for interpretative data.
+
+<p>
+<pre>
+<TMPL_VAR NAME="list">
+</pre>
+
+<TMPL_INCLUDE NAME="templates/v2legend.html">
+
+<hr>
+Created by <a href="http://www.palfrader.org/echolot/">Echolot <TMPL_VAR NAME="version"></a>.<br>
+Last update: <TMPL_VAR NAME="CURRENT_TIMESTAMP">.
+</body>
+
+<!-- vim:set ts=2: -->
+<!-- vim:set shiftwidth=2: -->
+</html>
diff --git a/trunk/templates/echolot.css b/trunk/templates/echolot.css
new file mode 100644
index 0000000..7806851
--- /dev/null
+++ b/trunk/templates/echolot.css
@@ -0,0 +1,10 @@
+body { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 15px; font-style: normal; line-height: normal; font-weight: normal; font-variant: normal; text-transform: none}
+h1 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 21px; font-style: normal; line-height: normal; font-weight: normal; font-variant: normal; text-transform: none }
+h2 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 19px; font-style: normal; line-height: normal; font-weight: normal; font-variant: normal; text-transform: none }
+h3 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 17px; font-style: normal; line-height: normal; font-weight: normal; font-variant: normal; text-transform: none }
+.tablehead { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 15px; font-style: normal; line-height: normal; font-weight: bolder; font-variant: normal; text-transform: none ; text-decoration: none}
+td { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 15px; font-style: normal; line-height: normal; font-weight: normal; font-variant: normal; text-transform: none }
+ul { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 15px; font-style: normal; line-height: normal; font-weight: normal; font-variant: normal; text-transform: none }
+li { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 15px; font-style: normal; line-height: normal; font-weight: normal; font-variant: normal; text-transform: none }
+tr { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 15px; font-style: normal; line-height: normal; font-weight: normal; font-variant: normal; text-transform: none }
+a { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 15px; font-style: normal; line-height: normal; font-weight: normal; font-variant: normal; text-transform: none }
diff --git a/trunk/templates/echolot.html b/trunk/templates/echolot.html
new file mode 100644
index 0000000..90ece0f
--- /dev/null
+++ b/trunk/templates/echolot.html
@@ -0,0 +1,144 @@
+<html>
+<head>
+ <title>Remailer Reliability Stats [<TMPL_VAR NAME="SITE_NAME">]</title>
+ <META HTTP-EQUIV="Expires" CONTENT="<TMPL_VAR NAME="EXPIRES">">
+ <link href="echolot.css" rel="stylesheet" type="text/css">
+ <!-- Pinger-Version: Echolot <TMPL_VAR NAME="version">
+ -->
+ <!-- $Id$ -->
+</head>
+<body>
+<h1>Remailer Reliability Stats [<TMPL_VAR NAME="SITE_NAME">]</h1>
+
+<p>
+This pinger automatically collects and generates remailer reliability statistics
+for both Type I (Cypherpunk) and Type II (Mixmaster) remailers. It also
+offers current keyrings <TMPL_IF NAME="thesaurus">and each remailer's reply to <code>remailer-xxx</code>
+queries </TMPL_IF>for your convenience.
+
+<h2>Available Stats</h2>
+
+<p>
+Out of the <TMPL_VAR NAME="CPUNK_TOTAL"> Cypherpunk and
+<TMPL_VAR NAME="MIX_TOTAL"> Mixmaster remailers
+(<TMPL_VAR NAME="UNIQUE_ADDRESSES"> unique addresses) on these stats there
+are <TMPL_VAR NAME="CPUNK_98"> CPunks and <TMPL_VAR NAME="MIX_98"> Mixes
+over 98.0% in terms of overall reliability.
+
+<h3>HTML</h3>
+<table border=1>
+<tr>
+ <th>Cypherpunk (Type I)</th>
+ <th>Mixmaster (Type II)</th>
+<TMPL_IF NAME="combined_list">
+ <th>Combined</th>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist.html"><code>rlist.html</code></a>
+<TMPL_IF NAME="separate_rlist">
+ <SMALL>
+ (<a href="rlist-rsa.html">rsa</a>,
+ <a href="rlist-dsa.html">dsa</a>,
+ <a href="rlist-clear.html">unencrypted</a>)
+ </SMALL>
+</TMPL_IF>
+ </td>
+ <td><a href="mlist.html"><code>mlist.html</code></a></td>
+<TMPL_IF NAME="combined_list">
+ <td rowspan=2><a href="clist.html"><code>clist2.html</code></a></td>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist2.html"><code>rlist2.html</code></a>
+<TMPL_IF NAME="separate_rlist">
+ <SMALL>
+ (<a href="rlist2-rsa.html">rsa</a>,
+ <a href="rlist2-dsa.html">dsa</a>,
+ <a href="rlist2-clear.html">unencrypted</a>)
+ </SMALL>
+</TMPL_IF>
+ </td>
+ <td><a href="mlist2.html"><code>mlist2.html</code></a></td>
+</tr>
+</table>
+
+<h3>Plain Text</h3>
+<table border=1>
+<tr>
+ <th>Cypherpunk (Type I)</th>
+ <th>Mixmaster (Type II)</th>
+<TMPL_IF NAME="combined_list">
+ <th>Combined</th>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist.txt"><code>rlist.txt</code></a>
+<TMPL_IF NAME="separate_rlist">
+ <SMALL>
+ (<a href="rlist-rsa.txt">rsa</a>,
+ <a href="rlist-dsa.txt">dsa</a>,
+ <a href="rlist-clear.txt">unencrypted</a>)
+ </SMALL>
+</TMPL_IF>
+ </td>
+ <td><a href="mlist.txt"><code>mlist.txt</code></a></td>
+<TMPL_IF NAME="combined_list">
+ <td rowspan=2><a href="clist.txt"><code>clist2.txt</code></a></td>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist2.txt"><code>rlist2.txt</code></a>
+<TMPL_IF NAME="separate_rlist">
+ <SMALL>
+ (<a href="rlist2-rsa.txt">rsa</a>,
+ <a href="rlist2-dsa.txt">dsa</a>,
+ <a href="rlist2-clear.txt">unencrypted</a>)
+ </SMALL>
+</TMPL_IF>
+ </td>
+ <td><a href="mlist2.txt"><code>mlist2.txt</code></a></td>
+</tr>
+</table>
+
+
+<h2>Available Keyrings</h2>
+<table border=1>
+ <tr>
+ <th>Cypherpunk (Type I)</th>
+ <th>Mixmaster (Type II)</th>
+ </tr>
+ <tr>
+ <td><a href="pgp-rsa.asc">RSA only</a></td>
+ <td><a href="pubring.mix"><code>pubring.mix</code></a></td>
+ </tr>
+ <tr>
+ <td><a href="pgp-all.asc">RSA and DSA</a></td>
+ <td><a href="type2.list"><code>type2.list</code></a></td>
+ </tr>
+</table>
+
+<TMPL_IF NAME="thesaurus">
+<h2>Thesaurus</h2>
+
+Find the Thesaurus index in <a href="thesaurus/">thesaurus/</a>.
+</TMPL_IF>
+
+<TMPL_IF NAME="fromlines">
+<h2>From Header Lines</h2>
+
+A summary of <a href="from.html">From Headers</a> each remailer uses has also
+been compiled. It also lists which remailers allow setting the From: Header.
+</TMPL_IF>
+
+<hr>
+Created by <a href="http://www.palfrader.org/echolot/">Echolot <TMPL_VAR
+NAME="version"></a> at <TMPL_VAR NAME="CURRENT_TIMESTAMP"> (individual pages
+may be newer).<br>
+This pinger is operated by <code><TMPL_VAR NAME="operator"></code>.<br>
+All times in Coordinated Universal Time (UTC).
+</body>
+
+<!-- vim:set ts=2: -->
+<!-- vim:set shiftwidth=2: -->
+</html>
diff --git a/trunk/templates/fromlinesindex.html b/trunk/templates/fromlinesindex.html
new file mode 100644
index 0000000..bac9423
--- /dev/null
+++ b/trunk/templates/fromlinesindex.html
@@ -0,0 +1,70 @@
+<html>
+<head>
+ <title>From Headers [<TMPL_VAR NAME="SITE_NAME">]</title>
+ <META HTTP-EQUIV="Expires" CONTENT="<TMPL_VAR NAME="EXPIRES">">
+ <link href="echolot.css" rel="stylesheet" type="text/css">
+ <!-- $Id$ -->
+</head>
+<body>
+<h1>From Headers [<TMPL_VAR NAME="SITE_NAME">]</h1>
+
+<p>
+<a href="./">Up</a>
+
+<h2>Default From: Headers</h2>
+
+<p>
+The default From headers of all remailers when sending e-mail. Middleman
+remailers are not listed here.
+
+<p>
+<table border=1>
+<tr><tr><th>nick</th><th>Disclaimer</th><th>From Line</th><th>type</th></tr>
+
+<TMPL_LOOP NAME="default">
+ <TMPL_LOOP NAME="data">
+ <tr>
+ <td><TMPL_VAR NAME="nick"></td>
+ <td align="center"><TMPL_IF NAME="disclaim_top">top</TMPL_IF>&nbsp;<TMPL_IF NAME="disclaim_bot">bot</TMPL_IF></td>
+ <td><TMPL_VAR NAME="from"></td>
+ <td><small><TMPL_VAR NAME="types"></small></td>
+ </tr>
+ </TMPL_LOOP>
+</TMPL_LOOP>
+
+</table>
+
+<h2>User Supplied From: Headers</h2>
+
+<p>
+If a message sent with a user supplied From header differs from
+one without, the remailer is listed here. The difference can either
+be an additional disclaimer, actually allowing (partial) From headers,
+etc.
+
+<p>
+<table border=1>
+
+<tr><tr><th>nick</th><th>Disclaimer</th><th>From Line</th><th>type</th></tr>
+
+<TMPL_LOOP NAME="usersupplied">
+ <TMPL_LOOP NAME="data">
+ <tr>
+ <td><TMPL_VAR NAME="nick"></td>
+ <td align="center"><TMPL_IF NAME="disclaim_top">top</TMPL_IF>&nbsp;<TMPL_IF NAME="disclaim_bot">bot</TMPL_IF></td>
+ <td><TMPL_VAR NAME="from"></td>
+ <td><small><TMPL_VAR NAME="types"></small></td>
+ </tr>
+ </TMPL_LOOP>
+</TMPL_LOOP>
+
+</table>
+
+<hr>
+Created by <a href="http://www.palfrader.org/echolot/">Echolot <TMPL_VAR NAME="version"></a>.<br>
+Last update: <TMPL_VAR NAME="CURRENT_TIMESTAMP">.
+</body>
+
+<!-- vim:set ts=2: -->
+<!-- vim:set shiftwidth=2: -->
+</html>
diff --git a/trunk/templates/mlist.html b/trunk/templates/mlist.html
new file mode 100644
index 0000000..fd4a822
--- /dev/null
+++ b/trunk/templates/mlist.html
@@ -0,0 +1,72 @@
+<html>
+<head>
+ <title>Mixmaster Remailers [<TMPL_VAR NAME="SITE_NAME">]</title>
+ <META HTTP-EQUIV="Expires" CONTENT="<TMPL_VAR NAME="EXPIRES">">
+ <link href="echolot.css" rel="stylesheet" type="text/css">
+ <!-- $Id$ -->
+</head>
+<body>
+<h1>Remailer Reliability Stats [<TMPL_VAR NAME="SITE_NAME">]</h1>
+
+<p>
+<a HREF="./">Up</a> <a href="mlist.txt">Plaintext version</a>
+
+<p>
+Available Stats:
+<table border=1>
+<tr>
+<TMPL_IF NAME="separate_rlist">
+ <th colspan=4>Cypherpunk (Type I)</th>
+<TMPL_ELSE>
+ <th>Cypherpunk (Type I)</th>
+</TMPL_IF>
+ <th>Mixmaster (Type II)</th>
+<TMPL_IF NAME="combined_list">
+ <th>Combined</th>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist.html">v1</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist-rsa.html">(rsa)</a></td>
+ <td><a href="rlist-dsa.html">(dsa)</a></td>
+ <td><a href="rlist-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><strong>v1</strong></td>
+<TMPL_IF NAME="combined_list">
+ <td rowspan=2><a href="clist.html">V2</a></td>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist2.html">v2</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist2-rsa.html">(rsa)</a></td>
+ <td><a href="rlist2-dsa.html">(dsa)</a></td>
+ <td><a href="rlist2-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist2.html">v2</a></td>
+</tr>
+</table>
+
+<h2>Mixmaster Remailers</h2>
+
+<p>
+This is an automatically generated list of remailer reliability
+statistics.
+Please see the <a href="#legend">Legend</a> below for interpretative data.
+
+<p>
+<pre>
+<TMPL_VAR NAME="list">
+</pre>
+
+<TMPL_INCLUDE NAME="templates/v1legend.html">
+
+<hr>
+Created by <a href="http://www.palfrader.org/echolot/">Echolot <TMPL_VAR NAME="version"></a>.<br>
+Last update: <TMPL_VAR NAME="CURRENT_TIMESTAMP">.
+</body>
+
+<!-- vim:set ts=2: -->
+<!-- vim:set shiftwidth=2: -->
+</html>
diff --git a/trunk/templates/mlist2.html b/trunk/templates/mlist2.html
new file mode 100644
index 0000000..978e5ee
--- /dev/null
+++ b/trunk/templates/mlist2.html
@@ -0,0 +1,72 @@
+<html>
+<head>
+ <title>Mixmaster Remailers (v2) [<TMPL_VAR NAME="SITE_NAME">]</title>
+ <META HTTP-EQUIV="Expires" CONTENT="<TMPL_VAR NAME="EXPIRES">">
+ <link href="echolot.css" rel="stylesheet" type="text/css">
+ <!-- $Id$ -->
+</head>
+<body>
+<h1>Remailer Reliability Stats [<TMPL_VAR NAME="SITE_NAME">]</h1>
+
+<p>
+<a HREF="./">Up</a> <a href="mlist2.txt">Plaintext version</a>
+
+<p>
+Available Stats:
+<table border=1>
+<tr>
+<TMPL_IF NAME="separate_rlist">
+ <th colspan=4>Cypherpunk (Type I)</th>
+<TMPL_ELSE>
+ <th>Cypherpunk (Type I)</th>
+</TMPL_IF>
+ <th>Mixmaster (Type II)</th>
+<TMPL_IF NAME="combined_list">
+ <th>Combined</th>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist.html">v1</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist-rsa.html">(rsa)</a></td>
+ <td><a href="rlist-dsa.html">(dsa)</a></td>
+ <td><a href="rlist-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist.html">v1</a></td>
+<TMPL_IF NAME="combined_list">
+ <td rowspan=2><a href="clist.html">V2</a></td>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist2.html">v2</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist2-rsa.html">(rsa)</a></td>
+ <td><a href="rlist2-dsa.html">(dsa)</a></td>
+ <td><a href="rlist2-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><strong>v2</strong></td>
+</tr>
+</table>
+
+<h2>Mixmaster Remailers (v2)</h2>
+
+<p>
+This is an automatically generated list of remailer reliability
+statistics.
+Please see the <a href="#legend">Legend</a> below for interpretative data.
+
+<p>
+<pre>
+<TMPL_VAR NAME="list">
+</pre>
+
+<TMPL_INCLUDE NAME="templates/v2legend.html">
+
+<hr>
+Created by <a href="http://www.palfrader.org/echolot/">Echolot <TMPL_VAR NAME="version"></a>.<br>
+Last update: <TMPL_VAR NAME="CURRENT_TIMESTAMP">.
+</body>
+
+<!-- vim:set ts=2: -->
+<!-- vim:set shiftwidth=2: -->
+</html>
diff --git a/trunk/templates/rlist-clear.html b/trunk/templates/rlist-clear.html
new file mode 100644
index 0000000..814b17a
--- /dev/null
+++ b/trunk/templates/rlist-clear.html
@@ -0,0 +1,72 @@
+<html>
+<head>
+ <title>Cypherpunk Remailers (cleartext only) [<TMPL_VAR NAME="SITE_NAME">]</title>
+ <META HTTP-EQUIV="Expires" CONTENT="<TMPL_VAR NAME="EXPIRES">">
+ <link href="echolot.css" rel="stylesheet" type="text/css">
+ <!-- $Id$ -->
+</head>
+<body>
+<h1>Remailer Reliability Stats [<TMPL_VAR NAME="SITE_NAME">]</h1>
+
+<p>
+<a HREF="./">Up</a> <a href="rlist-clear.txt">Plaintext version</a>
+
+<p>
+Available Stats:
+<table border=1>
+<tr>
+<TMPL_IF NAME="separate_rlist">
+ <th colspan=4>Cypherpunk (Type I)</th>
+<TMPL_ELSE>
+ <th>Cypherpunk (Type I)</th>
+</TMPL_IF>
+ <th>Mixmaster (Type II)</th>
+<TMPL_IF NAME="combined_list">
+ <th>Combined</th>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist.html">v1</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist-rsa.html">(rsa)</a></td>
+ <td><a href="rlist-dsa.html">(dsa)</a></td>
+ <td><strong>(cleartext)</strong></td>
+</TMPL_IF>
+ <td><a href="mlist.html">v1</a></td>
+<TMPL_IF NAME="combined_list">
+ <td rowspan=2><a href="clist.html">V2</a></td>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist2.html">v2</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist2-rsa.html">(rsa)</a></td>
+ <td><a href="rlist2-dsa.html">(dsa)</a></td>
+ <td><a href="rlist2-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist2.html">v2</a></td>
+</tr>
+</table>
+
+<h2>Cypherpunk Remailers (cleartext only)</h2>
+
+<p>
+This is an automatically generated list of remailer reliability
+statistics.
+Please see the <a href="#legend">Legend</a> below for interpretative data.
+
+<p>
+<pre>
+<TMPL_VAR NAME="list">
+</pre>
+
+<TMPL_INCLUDE NAME="templates/v1legend.html">
+
+<hr>
+Created by <a href="http://www.palfrader.org/echolot/">Echolot <TMPL_VAR NAME="version"></a>.<br>
+Last update: <TMPL_VAR NAME="CURRENT_TIMESTAMP">.
+</body>
+
+<!-- vim:set ts=2: -->
+<!-- vim:set shiftwidth=2: -->
+</html>
diff --git a/trunk/templates/rlist-dsa.html b/trunk/templates/rlist-dsa.html
new file mode 100644
index 0000000..2282082
--- /dev/null
+++ b/trunk/templates/rlist-dsa.html
@@ -0,0 +1,72 @@
+<html>
+<head>
+ <title>Cypherpunk Remailers (DSA only) [<TMPL_VAR NAME="SITE_NAME">]</title>
+ <META HTTP-EQUIV="Expires" CONTENT="<TMPL_VAR NAME="EXPIRES">">
+ <link href="echolot.css" rel="stylesheet" type="text/css">
+ <!-- $Id$ -->
+</head>
+<body>
+<h1>Remailer Reliability Stats [<TMPL_VAR NAME="SITE_NAME">]</h1>
+
+<p>
+<a HREF="./">Up</a> <a href="rlist-dsa.txt">Plaintext version</a>
+
+<p>
+Available Stats:
+<table border=1>
+<tr>
+<TMPL_IF NAME="separate_rlist">
+ <th colspan=4>Cypherpunk (Type I)</th>
+<TMPL_ELSE>
+ <th>Cypherpunk (Type I)</th>
+</TMPL_IF>
+ <th>Mixmaster (Type II)</th>
+<TMPL_IF NAME="combined_list">
+ <th>Combined</th>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist.html">v1</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist-rsa.html">(rsa)</a></td>
+ <td><strong>(dsa)</strong></td>
+ <td><a href="rlist-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist.html">v1</a></td>
+<TMPL_IF NAME="combined_list">
+ <td rowspan=2><a href="clist.html">V2</a></td>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist2.html">v2</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist2-rsa.html">(rsa)</a></td>
+ <td><a href="rlist2-dsa.html">(dsa)</a></td>
+ <td><a href="rlist2-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist2.html">v2</a></td>
+</tr>
+</table>
+
+<h2>Cypherpunk Remailers (DSA only)</h2>
+
+<p>
+This is an automatically generated list of remailer reliability
+statistics.
+Please see the <a href="#legend">Legend</a> below for interpretative data.
+
+<p>
+<pre>
+<TMPL_VAR NAME="list">
+</pre>
+
+<TMPL_INCLUDE NAME="templates/v1legend.html">
+
+<hr>
+Created by <a href="http://www.palfrader.org/echolot/">Echolot <TMPL_VAR NAME="version"></a>.<br>
+Last update: <TMPL_VAR NAME="CURRENT_TIMESTAMP">.
+</body>
+
+<!-- vim:set ts=2: -->
+<!-- vim:set shiftwidth=2: -->
+</html>
diff --git a/trunk/templates/rlist-rsa.html b/trunk/templates/rlist-rsa.html
new file mode 100644
index 0000000..bf24a68
--- /dev/null
+++ b/trunk/templates/rlist-rsa.html
@@ -0,0 +1,72 @@
+<html>
+<head>
+ <title>Cypherpunk Remailers (RSA only) [<TMPL_VAR NAME="SITE_NAME">]</title>
+ <META HTTP-EQUIV="Expires" CONTENT="<TMPL_VAR NAME="EXPIRES">">
+ <link href="echolot.css" rel="stylesheet" type="text/css">
+ <!-- $Id$ -->
+</head>
+<body>
+<h1>Remailer Reliability Stats [<TMPL_VAR NAME="SITE_NAME">]</h1>
+
+<p>
+<a HREF="./">Up</a> <a href="rlist-rsa.txt">Plaintext version</a>
+
+<p>
+Available Stats:
+<table border=1>
+<tr>
+<TMPL_IF NAME="separate_rlist">
+ <th colspan=4>Cypherpunk (Type I)</th>
+<TMPL_ELSE>
+ <th>Cypherpunk (Type I)</th>
+</TMPL_IF>
+ <th>Mixmaster (Type II)</th>
+<TMPL_IF NAME="combined_list">
+ <th>Combined</th>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist.html">v1</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><strong>(rsa)</strong></td>
+ <td><a href="rlist-dsa.html">(dsa)</a></td>
+ <td><a href="rlist-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist.html">v1</a></td>
+<TMPL_IF NAME="combined_list">
+ <td rowspan=2><a href="clist.html">V2</a></td>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist2.html">v2</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist2-rsa.html">(rsa)</a></td>
+ <td><a href="rlist2-dsa.html">(dsa)</a></td>
+ <td><a href="rlist2-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist2.html">v2</a></td>
+</tr>
+</table>
+
+<h2>Cypherpunk Remailers (RSA only)</h2>
+
+<p>
+This is an automatically generated list of remailer reliability
+statistics.
+Please see the <a href="#legend">Legend</a> below for interpretative data.
+
+<p>
+<pre>
+<TMPL_VAR NAME="list">
+</pre>
+
+<TMPL_INCLUDE NAME="templates/v1legend.html">
+
+<hr>
+Created by <a href="http://www.palfrader.org/echolot/">Echolot <TMPL_VAR NAME="version"></a>.<br>
+Last update: <TMPL_VAR NAME="CURRENT_TIMESTAMP">.
+</body>
+
+<!-- vim:set ts=2: -->
+<!-- vim:set shiftwidth=2: -->
+</html>
diff --git a/trunk/templates/rlist.html b/trunk/templates/rlist.html
new file mode 100644
index 0000000..fbf379c
--- /dev/null
+++ b/trunk/templates/rlist.html
@@ -0,0 +1,72 @@
+<html>
+<head>
+ <title>Cypherpunk Remailers [<TMPL_VAR NAME="SITE_NAME">]</title>
+ <META HTTP-EQUIV="Expires" CONTENT="<TMPL_VAR NAME="EXPIRES">">
+ <link href="echolot.css" rel="stylesheet" type="text/css">
+ <!-- $Id$ -->
+</head>
+<body>
+<h1>Remailer Reliability Stats [<TMPL_VAR NAME="SITE_NAME">]</h1>
+
+<p>
+<a HREF="./">Up</a> <a href="rlist.txt">Plaintext version</a>
+
+<p>
+Available Stats:
+<table border=1>
+<tr>
+<TMPL_IF NAME="separate_rlist">
+ <th colspan=4>Cypherpunk (Type I)</th>
+<TMPL_ELSE>
+ <th>Cypherpunk (Type I)</th>
+</TMPL_IF>
+ <th>Mixmaster (Type II)</th>
+<TMPL_IF NAME="combined_list">
+ <th>Combined</th>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><strong>v1</strong></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist-rsa.html">(rsa)</a></td>
+ <td><a href="rlist-dsa.html">(dsa)</a></td>
+ <td><a href="rlist-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist.html">v1</a></td>
+<TMPL_IF NAME="combined_list">
+ <td rowspan=2><a href="clist.html">V2</a></td>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist2.html">v2</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist2-rsa.html">(rsa)</a></td>
+ <td><a href="rlist2-dsa.html">(dsa)</a></td>
+ <td><a href="rlist2-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist2.html">v2</a></td>
+</tr>
+</table>
+
+<h2>Cypherpunk Remailers</h2>
+
+<p>
+This is an automatically generated list of remailer reliability
+statistics.
+Please see the <a href="#legend">Legend</a> below for interpretative data.
+
+<p>
+<pre>
+<TMPL_VAR NAME="list">
+</pre>
+
+<TMPL_INCLUDE NAME="templates/v1legend.html">
+
+<hr>
+Created by <a href="http://www.palfrader.org/echolot/">Echolot <TMPL_VAR NAME="version"></a>.<br>
+Last update: <TMPL_VAR NAME="CURRENT_TIMESTAMP">.
+</body>
+
+<!-- vim:set ts=2: -->
+<!-- vim:set shiftwidth=2: -->
+</html>
diff --git a/trunk/templates/rlist2-clear.html b/trunk/templates/rlist2-clear.html
new file mode 100644
index 0000000..10064ba
--- /dev/null
+++ b/trunk/templates/rlist2-clear.html
@@ -0,0 +1,72 @@
+<html>
+<head>
+ <title>Cypherpunk Remailers (v2, cleartext only) [<TMPL_VAR NAME="SITE_NAME">]</title>
+ <META HTTP-EQUIV="Expires" CONTENT="<TMPL_VAR NAME="EXPIRES">">
+ <link href="echolot.css" rel="stylesheet" type="text/css">
+ <!-- $Id$ -->
+</head>
+<body>
+<h1>Remailer Reliability Stats [<TMPL_VAR NAME="SITE_NAME">]</h1>
+
+<p>
+<a HREF="./">Up</a> <a href="rlist2-clear.txt">Plaintext version</a>
+
+<p>
+Available Stats:
+<table border=1>
+<tr>
+<TMPL_IF NAME="separate_rlist">
+ <th colspan=4>Cypherpunk (Type I)</th>
+<TMPL_ELSE>
+ <th>Cypherpunk (Type I)</th>
+</TMPL_IF>
+ <th>Mixmaster (Type II)</th>
+<TMPL_IF NAME="combined_list">
+ <th>Combined</th>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist.html">v1</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist-rsa.html">(rsa)</a></td>
+ <td><a href="rlist-dsa.html">(dsa)</a></td>
+ <td><a href="rlist-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist.html">v1</a></td>
+<TMPL_IF NAME="combined_list">
+ <td rowspan=2><a href="clist.html">V2</a></td>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist2.html">v2</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist2-rsa.html">(rsa)</a></td>
+ <td><a href="rlist2-dsa.html">(dsa)</a></td>
+ <td><strong>(cleartext)</strong></td>
+</TMPL_IF>
+ <td><a href="mlist2.html">v2</a></td>
+</tr>
+</table>
+
+<h2>Cypherpunk Remailers (v2, cleartext only)</h2>
+
+<p>
+This is an automatically generated list of remailer reliability
+statistics.
+Please see the <a href="#legend">Legend</a> below for interpretative data.
+
+<p>
+<pre>
+<TMPL_VAR NAME="list">
+</pre>
+
+<TMPL_INCLUDE NAME="templates/v2legend.html">
+
+<hr>
+Created by <a href="http://www.palfrader.org/echolot/">Echolot <TMPL_VAR NAME="version"></a>.<br>
+Last update: <TMPL_VAR NAME="CURRENT_TIMESTAMP">.
+</body>
+
+<!-- vim:set ts=2: -->
+<!-- vim:set shiftwidth=2: -->
+</html>
diff --git a/trunk/templates/rlist2-dsa.html b/trunk/templates/rlist2-dsa.html
new file mode 100644
index 0000000..cc9e85b
--- /dev/null
+++ b/trunk/templates/rlist2-dsa.html
@@ -0,0 +1,72 @@
+<html>
+<head>
+ <title>Cypherpunk Remailers (v2, DSA only) [<TMPL_VAR NAME="SITE_NAME">]</title>
+ <META HTTP-EQUIV="Expires" CONTENT="<TMPL_VAR NAME="EXPIRES">">
+ <link href="echolot.css" rel="stylesheet" type="text/css">
+ <!-- $Id$ -->
+</head>
+<body>
+<h1>Remailer Reliability Stats [<TMPL_VAR NAME="SITE_NAME">]</h1>
+
+<p>
+<a HREF="./">Up</a> <a href="rlist2-dsa.txt">Plaintext version</a>
+
+<p>
+Available Stats:
+<table border=1>
+<tr>
+<TMPL_IF NAME="separate_rlist">
+ <th colspan=4>Cypherpunk (Type I)</th>
+<TMPL_ELSE>
+ <th>Cypherpunk (Type I)</th>
+</TMPL_IF>
+ <th>Mixmaster (Type II)</th>
+<TMPL_IF NAME="combined_list">
+ <th>Combined</th>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist.html">v1</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist-rsa.html">(rsa)</a></td>
+ <td><a href="rlist-dsa.html">(dsa)</a></td>
+ <td><a href="rlist-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist.html">v1</a></td>
+<TMPL_IF NAME="combined_list">
+ <td rowspan=2><a href="clist.html">V2</a></td>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist2.html">v2</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist2-rsa.html">(rsa)</a></td>
+ <td><strong>(dsa)</strong></td>
+ <td><a href="rlist2-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist2.html">v2</a></td>
+</tr>
+</table>
+
+<h2>Cypherpunk Remailers (v2, DSA only)</h2>
+
+<p>
+This is an automatically generated list of remailer reliability
+statistics.
+Please see the <a href="#legend">Legend</a> below for interpretative data.
+
+<p>
+<pre>
+<TMPL_VAR NAME="list">
+</pre>
+
+<TMPL_INCLUDE NAME="templates/v2legend.html">
+
+<hr>
+Created by <a href="http://www.palfrader.org/echolot/">Echolot <TMPL_VAR NAME="version"></a>.<br>
+Last update: <TMPL_VAR NAME="CURRENT_TIMESTAMP">.
+</body>
+
+<!-- vim:set ts=2: -->
+<!-- vim:set shiftwidth=2: -->
+</html>
diff --git a/trunk/templates/rlist2-rsa.html b/trunk/templates/rlist2-rsa.html
new file mode 100644
index 0000000..14dab04
--- /dev/null
+++ b/trunk/templates/rlist2-rsa.html
@@ -0,0 +1,72 @@
+<html>
+<head>
+ <title>Cypherpunk Remailers (v2, RSA only) [<TMPL_VAR NAME="SITE_NAME">]</title>
+ <META HTTP-EQUIV="Expires" CONTENT="<TMPL_VAR NAME="EXPIRES">">
+ <link href="echolot.css" rel="stylesheet" type="text/css">
+ <!-- $Id$ -->
+</head>
+<body>
+<h1>Remailer Reliability Stats [<TMPL_VAR NAME="SITE_NAME">]</h1>
+
+<p>
+<a HREF="./">Up</a> <a href="rlist2-rsa.txt">Plaintext version</a>
+
+<p>
+Available Stats:
+<table border=1>
+<tr>
+<TMPL_IF NAME="separate_rlist">
+ <th colspan=4>Cypherpunk (Type I)</th>
+<TMPL_ELSE>
+ <th>Cypherpunk (Type I)</th>
+</TMPL_IF>
+ <th>Mixmaster (Type II)</th>
+<TMPL_IF NAME="combined_list">
+ <th>Combined</th>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist.html">v1</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist-rsa.html">(rsa)</a></td>
+ <td><a href="rlist-dsa.html">(dsa)</a></td>
+ <td><a href="rlist-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist.html">v1</a></td>
+<TMPL_IF NAME="combined_list">
+ <td rowspan=2><a href="clist.html">V2</a></td>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist2.html">v2</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><strong>(rsa)</strong></td>
+ <td><a href="rlist2-dsa.html">(dsa)</a></td>
+ <td><a href="rlist2-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist2.html">v2</a></td>
+</tr>
+</table>
+
+<h2>Cypherpunk Remailers (v2, RSA only)</h2>
+
+<p>
+This is an automatically generated list of remailer reliability
+statistics.
+Please see the <a href="#legend">Legend</a> below for interpretative data.
+
+<p>
+<pre>
+<TMPL_VAR NAME="list">
+</pre>
+
+<TMPL_INCLUDE NAME="templates/v2legend.html">
+
+<hr>
+Created by <a href="http://www.palfrader.org/echolot/">Echolot <TMPL_VAR NAME="version"></a>.<br>
+Last update: <TMPL_VAR NAME="CURRENT_TIMESTAMP">.
+</body>
+
+<!-- vim:set ts=2: -->
+<!-- vim:set shiftwidth=2: -->
+</html>
diff --git a/trunk/templates/rlist2.html b/trunk/templates/rlist2.html
new file mode 100644
index 0000000..4e0b6e1
--- /dev/null
+++ b/trunk/templates/rlist2.html
@@ -0,0 +1,72 @@
+<html>
+<head>
+ <title>Cypherpunk Remailers (v2) [<TMPL_VAR NAME="SITE_NAME">]</title>
+ <META HTTP-EQUIV="Expires" CONTENT="<TMPL_VAR NAME="EXPIRES">">
+ <link href="echolot.css" rel="stylesheet" type="text/css">
+ <!-- $Id$ -->
+</head>
+<body>
+<h1>Remailer Reliability Stats [<TMPL_VAR NAME="SITE_NAME">]</h1>
+
+<p>
+<a HREF="./">Up</a> <a href="rlist2.txt">Plaintext version</a>
+
+<p>
+Available Stats:
+<table border=1>
+<tr>
+<TMPL_IF NAME="separate_rlist">
+ <th colspan=4>Cypherpunk (Type I)</th>
+<TMPL_ELSE>
+ <th>Cypherpunk (Type I)</th>
+</TMPL_IF>
+ <th>Mixmaster (Type II)</th>
+<TMPL_IF NAME="combined_list">
+ <th>Combined</th>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><a href="rlist.html">v1</a></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist-rsa.html">(rsa)</a></td>
+ <td><a href="rlist-dsa.html">(dsa)</a></td>
+ <td><a href="rlist-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist.html">v1</a></td>
+<TMPL_IF NAME="combined_list">
+ <td rowspan=2><a href="clist.html">V2</a></td>
+</TMPL_IF>
+</tr>
+<tr>
+ <td><strong>v2</strong></td>
+<TMPL_IF NAME="separate_rlist">
+ <td><a href="rlist2-rsa.html">(rsa)</a></td>
+ <td><a href="rlist2-dsa.html">(dsa)</a></td>
+ <td><a href="rlist2-clear.html">(cleartext)</a></td>
+</TMPL_IF>
+ <td><a href="mlist2.html">v2</a></td>
+</tr>
+</table>
+
+<h2>Cypherpunk Remailers (v2)</h2>
+
+<p>
+This is an automatically generated list of remailer reliability
+statistics.
+Please see the <a href="#legend">Legend</a> below for interpretative data.
+
+<p>
+<pre>
+<TMPL_VAR NAME="list">
+</pre>
+
+<TMPL_INCLUDE NAME="templates/v2legend.html">
+
+<hr>
+Created by <a href="http://www.palfrader.org/echolot/">Echolot <TMPL_VAR NAME="version"></a>.<br>
+Last update: <TMPL_VAR NAME="CURRENT_TIMESTAMP">.
+</body>
+
+<!-- vim:set ts=2: -->
+<!-- vim:set shiftwidth=2: -->
+</html>
diff --git a/trunk/templates/thesaurusindex.html b/trunk/templates/thesaurusindex.html
new file mode 100644
index 0000000..7474c2e
--- /dev/null
+++ b/trunk/templates/thesaurusindex.html
@@ -0,0 +1,40 @@
+<html>
+<head>
+ <title>Thesaurus [<TMPL_VAR NAME="SITE_NAME">]</title>
+ <META HTTP-EQUIV="Expires" CONTENT="<TMPL_VAR NAME="EXPIRES">">
+ <link href="../echolot.css" rel="stylesheet" type="text/css">
+ <!-- $Id$ -->
+</head>
+<body>
+<h1>Thesaurus [<TMPL_VAR NAME="SITE_NAME">]</h1>
+
+<p>
+<a href="../">Up</a>
+
+<p>
+<table border=1>
+
+<tr><tr><th>nick</th><th>Address</th><th>conf</th><th>help</th><th>key</th><th>stats</th><th>adminkey</th></tr>
+
+<TMPL_LOOP NAME="remailers">
+ <tr>
+ <td><TMPL_VAR NAME="nick"></td>
+ <td><TMPL_VAR NAME="address"></td>
+ <td align="center"><TMPL_IF NAME="conf_date"><a href="<TMPL_VAR NAME="conf_href">"><TMPL_VAR NAME="conf_date"><br><TMPL_VAR NAME="conf_time"></a><TMPL_ELSE>N/A</TMPL_IF></td>
+ <td align="center"><TMPL_IF NAME="help_date"><a href="<TMPL_VAR NAME="help_href">"><TMPL_VAR NAME="help_date"><br><TMPL_VAR NAME="help_time"></a><TMPL_ELSE>N/A</TMPL_IF></td>
+ <td align="center"><TMPL_IF NAME="key_date"><a href="<TMPL_VAR NAME="key_href">"><TMPL_VAR NAME="key_date"><br><TMPL_VAR NAME="key_time"></a><TMPL_ELSE>N/A</TMPL_IF></td>
+ <td align="center"><TMPL_IF NAME="stats_date"><a href="<TMPL_VAR NAME="stats_href">"><TMPL_VAR NAME="stats_date"><br><TMPL_VAR NAME="stats_time"></a><TMPL_ELSE>N/A</TMPL_IF></td>
+ <td align="center"><TMPL_IF NAME="adminkey_date"><a href="<TMPL_VAR NAME="adminkey_href">"><TMPL_VAR NAME="adminkey_date"><br><TMPL_VAR NAME="adminkey_time"></a><TMPL_ELSE>N/A</TMPL_IF></td>
+ </tr>
+</TMPL_LOOP>
+
+</table>
+
+<hr>
+Created by <a href="http://www.palfrader.org/echolot/">Echolot <TMPL_VAR NAME="version"></a>.<br>
+Last update: <TMPL_VAR NAME="CURRENT_TIMESTAMP">.
+</body>
+
+<!-- vim:set ts=2: -->
+<!-- vim:set shiftwidth=2: -->
+</html>
diff --git a/trunk/templates/v1legend.html b/trunk/templates/v1legend.html
new file mode 100644
index 0000000..0d63a02
--- /dev/null
+++ b/trunk/templates/v1legend.html
@@ -0,0 +1,22 @@
+<H2><a name="legend">Legend</a></H2>
+<!-- $Id$ -->
+<p>
+<strong>History</strong>: The result of test messages sent in the last 12 days.
+<p>
+<BLOCKQUOTE>
+<TABLE CELLPADDING=0 CELLSPACING=0 border="0">
+<TR><TD>?</TD><TD>No test message sent</TD></TR>
+<TR><TD>(space)</TD><TD>Response not received</TD></TR>
+<TR><TD>#</TD><TD>Response in less than 5 minutes</TD></TR>
+<TR><TD>*</TD><TD>Response in less than 1 hour</TD></TR>
+<TR><TD>+</TD><TD>Response in less than 4 hours</TD></TR>
+<TR><TD>-</TD><TD>Response in less than 24 hours</TD></TR>
+<TR><TD>.</TD><TD>Response in less than 2 days</TD></TR>
+<TR><TD>_</TD><TD>Response in more than 2 days</TD></TR>
+</TABLE>
+</BLOCKQUOTE>
+<p>
+<strong>Latency</strong>: The average response time of the remailer.
+<p>
+<strong>Uptime</strong>: The fraction of responses received from tests sent in
+the last 12 days.
diff --git a/trunk/templates/v2legend.html b/trunk/templates/v2legend.html
new file mode 100644
index 0000000..d327eb3
--- /dev/null
+++ b/trunk/templates/v2legend.html
@@ -0,0 +1,85 @@
+<H2><a name="legend">Legend</a></H2>
+<!-- $Id$ -->
+<p>
+The stats table shows the 12-day performance history of each remailer.
+Pings (test messages) are sent to each remailer and response time is
+measured.
+
+<p>
+<strong>Latent-Hist</strong> shows the average default response time for each day:
+<blockquote>
+<table CELLPADDING=0 CELLSPACING=0>
+<tr><td>0</td><td>less than 20 minutes</td></tr>
+<tr><td>1</td><td>less than 1 hour</td></tr>
+<tr><td>...</td></tr>
+<tr><td>9</td><td>less than 9 hours</td></tr>
+
+<tr><td>A</td><td>less than 12 hours</td></tr>
+<tr><td>B</td><td>less than 18 hours</td></tr>
+<tr><td>C</td><td>less than 24 hours</td></tr>
+<tr><td>...</td></tr>
+<tr><td>G</td><td>less than 48 hours</td></tr>
+<tr><td>H</td><td>more than 48 hours</td></tr>
+
+<tr><td>?</td><td>No responses received / No data</td></tr>
+</table>
+</blockquote>
+
+<p>
+<strong>Latent</strong> shows the average default response time in HH:MM format.
+
+<p>
+<strong>Uptime-Hist</strong> shows the Uptime percentage (responses received divided by pings
+sent) for each day:
+<blockquote>
+<table CELLPADDING=0 CELLSPACING=0>
+<tr><td>+</td><td>100% (Responses were received for all pings sent)</td></tr>
+<tr><td>9</td><td>90-99.9% (About 9 responses were received for every 10 pings)</td></tr>
+<tr><td>...</td></tr>
+<tr><td>1</td><td>10-19.9%</td></tr>
+
+<tr><td>0</td><td>0-9.9%</td></tr>
+<tr><td>?</td><td>No pings sent / No data</td></tr>
+</table>
+</blockquote>
+
+<p>
+<strong>Uptime</strong> shows the average Uptime percentage for 12 days.
+
+<p>
+<strong>Options</strong> shows an abbreviated form of the strings listed in the
+Remailer-Capabilities section above:
+<blockquote>
+<table CELLPADDING=0 CELLSPACING=0>
+<tr><td>D</td><td>middle (Remailer is middleman and chains to other remailers)</td></tr>
+<tr><td>P</td><td>post (Supports news posting (Anon-Post-To or Post)</td></tr>
+
+<tr><td>M/R/2</td><td>mix/remix/remix2 (Supported Mixmaster features)</td></tr>
+<tr><td>H</td><td>hybrid (Supports CPunk directives in Mix messages)</td></tr>
+<tr><td>G/2</td><td>repgp/repgp2</td></tr>
+<tr><td>O</td><td>pgponly (Requires Cypherpunk messages to be PGP encrypted)</td></tr>
+<tr><td>X</td><td>ext (Supports extended directive features)</td></tr>
+<tr><td>A</td><td>max (Supports Max-Size, Max-Count, and Max-Date directives)</td></tr>
+
+<tr><td>T</td><td>test (Supports the Test-To directive)</td></tr>
+<tr><td>L</td><td>latent (Supports the Latent-Time directive)</td></tr>
+<tr><td>e/E</td><td>ek/ekx (Supports Encrypt-Key/-3DES,-CAST directives)</td></tr>
+<tr><td>U</td><td>esub (Supports the Encrypt-Subject directive)</td></tr>
+<tr><td>I</td><td>inflt (Supports the Inflate directive)</td></tr>
+<tr><td>N</td><td>rhop (Supports the Rand-Hop directive)</td></tr>
+
+<tr><td VALIGN=TOP>#</td><td>klen - The digit indicates the maximum message size:
+ <blockquote>
+ <table CELLPADDING=0 CELLSPACING=0>
+ <tr><td>9</td><td>Max is greater than 900K</td></tr>
+ <tr><td>8</td><td>Max is less than 900K</td></tr>
+ <tr><td>...</td></tr>
+ <tr><td>1</td><td>Max is less than 200K</td></tr>
+
+ <tr><td>0</td><td>Max is less than 100K</td></tr>
+ </table>
+ </blockquote>
+ </td></tr>
+</table>
+</blockquote>
+
diff --git a/trunk/tools/create-distribution b/trunk/tools/create-distribution
new file mode 100755
index 0000000..8324f8c
--- /dev/null
+++ b/trunk/tools/create-distribution
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+# This script is used after exporting the CVS to build a new
+# release tarball
+
+set -e
+
+version=`grep 'VERSION =' pingd | sed -e "s/.* '//" -e "s/'.*//"`
+dirname=`basename \`pwd\``
+pod2man --section=1 --release="$version" --center=Echolot pingd doc/pingd.1
+pod2man --section=5 --release="$version" --center=Echolot doc/pingd.conf.pod doc/pingd.conf.5
+
+if [ "$dirname" != "echolot-$version" ] ; then
+ echo "WARN: Version $version might be wrong"
+ echo "Enter to continue"
+ read
+fi
+
+cd ..
+tar czf $dirname.tar.gz $dirname
+gpg --detach-sign $dirname.tar.gz
diff --git a/trunk/tools/install-perl-modules b/trunk/tools/install-perl-modules
new file mode 100755
index 0000000..872b63e
--- /dev/null
+++ b/trunk/tools/install-perl-modules
@@ -0,0 +1,33 @@
+#! /usr/bin/perl -w
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+# This script installs all perl libraries Echolot needs
+# using your CPAN module
+
+use strict;
+use CPAN;
+
+for my $mod (qw{Data::Dumper Digest::MD5 HTML::Template GnuPG::Interface}) {
+ my $obj = CPAN::Shell->expand('Module',$mod);
+ $obj->install();
+};
diff --git a/trunk/tools/pingctl b/trunk/tools/pingctl
new file mode 100755
index 0000000..286201e
--- /dev/null
+++ b/trunk/tools/pingctl
@@ -0,0 +1,171 @@
+#!/bin/sh
+#
+# $Id$
+#
+# This file is part of Echolot - a Pinger for anonymous remailers.
+#
+# Copyright (c) 2002 admin@arancio.net
+# Copyright (c) 2002, 2003, 2004 Peter Palfrader <peter@palfrader.org>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+# pingcntl: echolot control + wrapper
+
+set -e
+
+################################################################
+# You perhaps want to change those items
+
+USER=pinger
+VERBOSE=0
+BASEDIR="/home/pinger/echolot"
+PINGD="$BASEDIR/pingd"
+PIDFILE="$BASEDIR/pingd.pid"
+
+# You probably don't want to mess with stuff below this line
+################################################################
+
+CHECKULIMIT=1
+CHECKUID=1
+
+
+wait_for_deaddaemon () {
+ PID=$1
+ sleep 3
+ if test -n "$PID"
+ then
+ if kill -0 $PID 2>/dev/null
+ then
+ echo -n "Waiting for pid $PID ."
+ cnt=0
+ while kill -0 $PID 2>/dev/null
+ do
+ cnt=`expr $cnt + 1`
+ if [ $cnt -gt 30 ]
+ then
+ echo " Failed.. "
+ exit 1;
+ fi
+ sleep 2
+ echo -n "."
+ done
+ rm -f $PIDFILE
+ else
+ rm -f $PIDFILE
+ fi
+ fi
+}
+
+
+# Check for evil ulimits
+if [ "$CHECKULIMIT" -gt "0" ]; then
+ FDs=`ulimit -n`
+ HFDs=`ulimit -H -n`
+ if [ "$FDs" -lt "512" ]; then
+ if [ "$HFDs" -lt "512" ]; then
+ echo "Hardlimit for open File Descriptors is less than 512." >&2
+ echo "Please consider raising it." >&2
+ if [ "$FDs" -lt "$HFDs" ]; then
+ echo "Raising it to $HFDs" >&2
+ ulimit -n $HFDs
+ fi
+ else
+ if [ "$HFDs" -lt "1024" ]; then
+ FDs=$HFDs
+ else
+ FDs=1024
+ fi
+ echo "Softlimit for open File Descriptors is less than 512." >&2
+ echo "Raising it to $FDs" >&2
+ ulimit -n $FDs
+ fi
+ fi
+fi
+
+
+# Check for right User
+SU=""
+if [ "$CHECKUID" -gt "0" ]; then
+ CUID=`id -u`
+ CUIDNAME=`id -nu`
+ if [ "$CUIDNAME" = "$USER" ]; then
+ SU=""
+ elif [ "$CUID" = "0" ]; then
+ SU="su $USER -c"
+ else
+ echo "You are neither $USER nor root. Aborting." >&2
+ exit 1;
+ fi
+fi
+
+# set VERBOSE
+if [ $VERBOSE -gt 0 ]; then
+ VERBOSE="--verbose"
+else
+ VERBOSE=""
+fi
+
+
+case $1 in
+
+start)
+ if [ -f $PIDFILE ] ; then
+ PID=`cat $PIDFILE 2>/dev/null` || true
+ if kill -0 $PID 2>/dev/null
+ then
+ echo "pingd already running."
+ exit 0
+ else
+ echo -n "Removing stale pid file: "
+ rm -f $PIDFILE
+ echo "$PIDFILE."
+ fi
+ fi
+ echo -n "Starting Echolot: pingd"
+ if [ ! -z "$SU" ]; then
+ $SU "$PINGD --detach $VERBOSE start"
+ else
+ $PINGD --detach $VERBOSE start
+ fi
+ echo "."
+ ;;
+
+stop)
+ echo -n "Stopping Echolot: pingd"
+ if [ ! -z "$SU" ]; then
+ $SU "$PINGD stop"
+ else
+ $PINGD stop
+ fi
+ echo "."
+ ;;
+
+reload|force-reload|restart)
+ PID=`cat $PIDFILE 2>/dev/null` || true
+ $0 stop
+ wait_for_deaddaemon $PID
+ $0 start
+ ;;
+*)
+ echo "Usage: $0 (start|stop|reload|force-reload|restart)" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
+
+# vim:set ts=2:
+# vim:set shiftwidth=2: