From 0a27014ee8ba24a3ca3d78cefdeda8ba391e42ba Mon Sep 17 00:00:00 2001 From: Peter Palfrader Date: Mon, 6 Mar 2006 15:10:03 +0000 Subject: Tag as release_2_1_8-4, again --- trunk/COPYING | 340 +++++++ trunk/Echolot/Chain.pm | 266 +++++ trunk/Echolot/Commands.pm | 131 +++ trunk/Echolot/Conf.pm | 531 ++++++++++ trunk/Echolot/Config.pm | 344 +++++++ trunk/Echolot/Fromlines.pm | 126 +++ trunk/Echolot/Globals.pm | 60 ++ trunk/Echolot/Log.pm | 163 +++ trunk/Echolot/Mailin.pm | 252 +++++ trunk/Echolot/Pinger.pm | 211 ++++ trunk/Echolot/Pinger/CPunk.pm | 205 ++++ trunk/Echolot/Pinger/Mix.pm | 139 +++ trunk/Echolot/Report.pm | 70 ++ trunk/Echolot/Scheduler.pm | 196 ++++ trunk/Echolot/Stats.pm | 983 ++++++++++++++++++ trunk/Echolot/Storage/File.pm | 1880 +++++++++++++++++++++++++++++++++++ trunk/Echolot/Thesaurus.pm | 144 +++ trunk/Echolot/Tools.pm | 476 +++++++++ trunk/LICENSE | 17 + trunk/NEWS | 415 ++++++++ trunk/README | 319 ++++++ trunk/TODO | 35 + trunk/UPGRADE | 21 + trunk/debian/README.Debian | 26 + trunk/debian/changelog | 299 ++++++ trunk/debian/control | 21 + trunk/debian/copyright | 24 + trunk/debian/echolot.default | 12 + trunk/debian/echolot.dirs | 7 + trunk/debian/echolot.docs | 3 + trunk/debian/echolot.init | 174 ++++ trunk/debian/echolot.links | 1 + trunk/debian/echolot.logrotate | 11 + trunk/debian/echolot.manpages | 2 + trunk/debian/echolot.postinst | 41 + trunk/debian/echolot.postrm | 28 + trunk/debian/pingd.conf | 25 + trunk/debian/rules | 77 ++ trunk/doc/methodology | 60 ++ trunk/doc/pingd.conf.pod | 910 +++++++++++++++++ trunk/pingd | 854 ++++++++++++++++ trunk/pingd.conf.sample | 72 ++ trunk/templates/LICENSE | 39 + trunk/templates/clist.html | 72 ++ trunk/templates/echolot.css | 10 + trunk/templates/echolot.html | 144 +++ trunk/templates/fromlinesindex.html | 70 ++ trunk/templates/mlist.html | 72 ++ trunk/templates/mlist2.html | 72 ++ trunk/templates/rlist-clear.html | 72 ++ trunk/templates/rlist-dsa.html | 72 ++ trunk/templates/rlist-rsa.html | 72 ++ trunk/templates/rlist.html | 72 ++ trunk/templates/rlist2-clear.html | 72 ++ trunk/templates/rlist2-dsa.html | 72 ++ trunk/templates/rlist2-rsa.html | 72 ++ trunk/templates/rlist2.html | 72 ++ trunk/templates/thesaurusindex.html | 40 + trunk/templates/v1legend.html | 22 + trunk/templates/v2legend.html | 85 ++ trunk/tools/create-distribution | 42 + trunk/tools/install-perl-modules | 33 + trunk/tools/pingctl | 171 ++++ 63 files changed, 11419 insertions(+) create mode 100644 trunk/COPYING create mode 100644 trunk/Echolot/Chain.pm create mode 100644 trunk/Echolot/Commands.pm create mode 100644 trunk/Echolot/Conf.pm create mode 100644 trunk/Echolot/Config.pm create mode 100644 trunk/Echolot/Fromlines.pm create mode 100644 trunk/Echolot/Globals.pm create mode 100644 trunk/Echolot/Log.pm create mode 100644 trunk/Echolot/Mailin.pm create mode 100644 trunk/Echolot/Pinger.pm create mode 100644 trunk/Echolot/Pinger/CPunk.pm create mode 100644 trunk/Echolot/Pinger/Mix.pm create mode 100644 trunk/Echolot/Report.pm create mode 100644 trunk/Echolot/Scheduler.pm create mode 100644 trunk/Echolot/Stats.pm create mode 100644 trunk/Echolot/Storage/File.pm create mode 100644 trunk/Echolot/Thesaurus.pm create mode 100644 trunk/Echolot/Tools.pm create mode 100644 trunk/LICENSE create mode 100644 trunk/NEWS create mode 100644 trunk/README create mode 100644 trunk/TODO create mode 100644 trunk/UPGRADE create mode 100644 trunk/debian/README.Debian create mode 100644 trunk/debian/changelog create mode 100644 trunk/debian/control create mode 100644 trunk/debian/copyright create mode 100644 trunk/debian/echolot.default create mode 100644 trunk/debian/echolot.dirs create mode 100644 trunk/debian/echolot.docs create mode 100755 trunk/debian/echolot.init create mode 100644 trunk/debian/echolot.links create mode 100644 trunk/debian/echolot.logrotate create mode 100644 trunk/debian/echolot.manpages create mode 100755 trunk/debian/echolot.postinst create mode 100755 trunk/debian/echolot.postrm create mode 100644 trunk/debian/pingd.conf create mode 100755 trunk/debian/rules create mode 100644 trunk/doc/methodology create mode 100644 trunk/doc/pingd.conf.pod create mode 100755 trunk/pingd create mode 100644 trunk/pingd.conf.sample create mode 100644 trunk/templates/LICENSE create mode 100644 trunk/templates/clist.html create mode 100644 trunk/templates/echolot.css create mode 100644 trunk/templates/echolot.html create mode 100644 trunk/templates/fromlinesindex.html create mode 100644 trunk/templates/mlist.html create mode 100644 trunk/templates/mlist2.html create mode 100644 trunk/templates/rlist-clear.html create mode 100644 trunk/templates/rlist-dsa.html create mode 100644 trunk/templates/rlist-rsa.html create mode 100644 trunk/templates/rlist.html create mode 100644 trunk/templates/rlist2-clear.html create mode 100644 trunk/templates/rlist2-dsa.html create mode 100644 trunk/templates/rlist2-rsa.html create mode 100644 trunk/templates/rlist2.html create mode 100644 trunk/templates/thesaurusindex.html create mode 100644 trunk/templates/v1legend.html create mode 100644 trunk/templates/v2legend.html create mode 100755 trunk/tools/create-distribution create mode 100755 trunk/tools/install-perl-modules create mode 100755 trunk/tools/pingctl 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. + + + Copyright (C) + + 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. + + , 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 +# +# 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 +# +# 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 () { + 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 +# +# 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 +# +# 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 environment variable + +=item /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". + " 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 .\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 = ; + 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 +# +# 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 +# +# 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 +# +# 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 +# +# 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 = ; + my $body = join('', ); + 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() { + 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 = ; + 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 +# +# 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 +# +# 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 '', ; + 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 +# +# 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 +# +# 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 +# +# 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 () + +Creates a new scheduler object. + +=cut +sub new { + my ($class, %params) = @_; + my $self = {}; + bless $self, $class; + return $self; +}; + +=item B (I, I, I, I, I) + +Adds a task with I to the list of tasks. Every I seconds +I is called. If for example I is 3600 - meaning I +should be executed hourly - setting I to 600 would mean that +it get's called 10 minutes after the hour. + +I 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 (I, I, [ I, [I]] ) + +Schedule execution of I for I. If I is not given it is calculated +from I and I passed to B. if I 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 () + +Start the scheduling run. + +It will run forever or until a task with I == '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 +# +# 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/&/&/g; + $output =~ s/"/"/g; + $output =~ s//>/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 = ; + 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 +# +# 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 (I<%args>) + +Creates a new storage backend object. +args keys: + +=over + +=item I + +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( ) + +Write metadata unless B 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( ) + +Increase B by one. + +=cut +sub delay_commit($) { + my ($self) = @_; + + $self->{'DELAY_COMMIT'}++; +}; + +=item $storage->B( I<$set_> ) + +Decrease B by one and call C if B 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( ) + +Shut down cleanly. + +=cut +sub finish($) { + my ($self) = @_; + + $self->pingdata_close(); + $self->chainpingdata_close(); + $self->metadata_write(); + $self->metadata_close(); +}; + + + + +=item $storage->B( ) + +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( ) + +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( ) + +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( ) + +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( ) + +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( 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( ) + +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( 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 $ 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( 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( ) + +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( 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 then it's an array of scalar (the send timestamps). + +If direction is B 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( I<$remailer_addr>, I<$type>, I<$key>, I<$sent_time> ) + +Register a ping sent to I<$remailer_addr>, I<$type>, I<$key> and I$. + +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( 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$ 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( 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( ) + +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( 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( 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( ) + +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( 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, B, B, B, +B, B, B, and in case of received pings B. + +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( 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$. + +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( 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$ +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( 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 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( 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 an array of all remailers we know about. Each element in this array is a +hash reference as returned by C. + +=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( I<$addr> ) + +Adds a remailer with address I<$addr>. B, B, and B are +set to the values configured for new remailers. + +Assign the remailer status B and a new unique ID. + +See L 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( 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. Recognised keys are B, +B, and B. Acceptable values are B and B. + +See L 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( 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( 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. + +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( 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. + +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( I<$address> ) + +Restore the TTL (Time To Live) for remailer with address I<$address> to the +value configured with I + +See L 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( I<$id> ) + +Set the remailer whoise id is I<$id> to B. + +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( 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. + +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( 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( ) + +Return our secret (Used in Message Authentication Codes). + +=cut +sub get_secret($) { + my ($self) = @_; + + return $self->{'METADATA'}->{'secret'}; +}; + +=item $storage->B( 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( 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( 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( I<$remailer>, I<$type>, I<$key> ) + +Returns a hash having they keys C, C, C, and +C 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( 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( 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( ) + +Expires old keys, confs and pings from the Storage as configured by +I, I, and I. + +See L 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( 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 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( 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( I<$address>, I<$with_from>, I<$from>, $I, $I ) + +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 and $I 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( I<$addr>, I<$type>, I<$user_supplied> ) + +Return a hash reference with header From line information. + +The hash has two keys, B and B, 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 +# +# 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 +# +# 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/&/&/; + $in =~ s/"/"/; + $in =~ s//>/; + + 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 = ; + 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 + +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= 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 ... + + 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 . +- 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 +, 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 , 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 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 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 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 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 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 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 Sat, 7 Aug 2004 16:00:52 +0200 + +echolot (2.1.5-1) unstable; urgency=low + + * New upstream version. + + -- Peter Palfrader 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 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 Tue, 20 Apr 2004 13:42:32 +0200 + +echolot (2.1.2-1) unstable; urgency=low + + * New upstream version. + + -- Peter Palfrader Tue, 4 Nov 2003 05:16:07 +0100 + +echolot (2.1.1-1) unstable; urgency=low + + * New upstream version. + + -- Peter Palfrader 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 Sun, 12 Oct 2003 12:15:38 +0200 + +echolot (2.1-1) unstable; urgency=low + + * New upstream version. + + -- Peter Palfrader Mon, 3 Mar 2003 16:42:42 +0100 + +echolot (2.0.10-1) unstable; urgency=low + + * New upstream version. + + -- Peter Palfrader 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 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 Mon, 13 Jan 2003 15:25:27 +0100 + +echolot (2.0.7-1) unstable; urgency=low + + * New upstream version. + + -- Peter Palfrader 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 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 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 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 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 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 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 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 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 Sat, 12 Oct 2002 01:10:18 +0200 + +echolot (2.0.2-1) unstable; urgency=low + + * New upstream version. + + -- Peter Palfrader Sat, 21 Sep 2002 05:11:37 +0200 + +echolot (2.0.1-1) unstable; urgency=low + + * New upstream version. + + -- Peter Palfrader 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 Tue, 17 Sep 2002 06:17:43 +0200 + +echolot (000.2.0rc3-1) unstable; urgency=low + + * New upstream version. + + -- Peter Palfrader Fri, 13 Sep 2002 01:16:21 +0200 + +echolot (000.2.0rc2-1) unstable; urgency=low + + * New upstream version. + + -- Peter Palfrader Sun, 8 Sep 2002 20:16:31 +0200 + +echolot (000.2.0rc1-1) unstable; urgency=low + + * New version number. + + -- Peter Palfrader Thu, 5 Sep 2002 16:51:42 +0200 + +echolot (000.2.0beta34-1) unstable; urgency=low + + * New upstream version + + -- Peter Palfrader Wed, 4 Sep 2002 13:22:13 +0200 + +echolot (000.2.0beta33-1) unstable; urgency=low + + * New upstream version + + -- Peter Palfrader Fri, 23 Aug 2002 09:54:10 +0200 + +echolot (000.2.0beta32-1) unstable; urgency=low + + * New upstream version + + -- Peter Palfrader Fri, 23 Aug 2002 08:05:26 +0200 + +echolot (000.2.0beta31-1) unstable; urgency=low + + * New upstream version + + -- Peter Palfrader Wed, 21 Aug 2002 21:45:37 +0200 + +echolot (000.2.0beta30-1) unstable; urgency=low + + * New upstream version + + -- Peter Palfrader Thu, 15 Aug 2002 05:28:11 +0200 + +echolot (000.2.0beta29-1) unstable; urgency=low + + * New upstream version + + -- Peter Palfrader Thu, 15 Aug 2002 00:54:31 +0200 + +echolot (000.2.0beta28-1) unstable; urgency=low + + * New upstream version + + -- Peter Palfrader 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 Tue, 13 Aug 2002 02:49:45 +0200 + +echolot (000.2.0beta26-1) unstable; urgency=low + + * Initial Release. + + -- Peter Palfrader 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 +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 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 + +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 [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/.*.*//' > 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 +# +# 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 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 + +A short name for your site/pinger. It is used in the statistics produced. + + Default: none + Example: 'sitename' => 'testsite', + +=item B + +The local part of the pinger's email address. + +In C the localpart is C. + + Default: none + Example: 'my_localpart' => 'pinger', + +=item B + +The domain part (FQDN) of the pinger's email address. + +In C the domain part is C. + + Default: none + Example: 'my_domain' => 'remailer.example.com', + +=item B + +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 + +The B 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 + +Where to read strong random data from - currently used only for generating our +secret. + + Default: 'dev_random' => '/dev/random', + +=item B + +Where to read weak random data from - currently used only for +garbage generation. + + Default: 'dev_urandom' => '/dev/urandom', + +=item B + +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 [integer] + +Echolot uses email addresses of the form C. 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. Echolot uses MD5 +as the MAC hash function. + +B is the number of characters to include in the email address. + + Default: 'hash_len' => 8, + Example: 'hash_len' => 4, + +=item B [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 [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 [bool] + +Query new remailers for remailer-xxx replies by default. + + Default: 'fetch_new' => 1, + Example: 'fetch_new' => 0, + +=item B [bool] + +Ping new remailers by default. + + Default: 'ping_new' => 1, + Example: 'ping_new' => 0, + +=item B [bool] + +Show new remailers in public stats by default. + + Default: 'show_new' => 1, + Example: 'show_new' => 0, + +=back + + + +=head2 STATISTICS GENERATION + +=over + +=item B [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 [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 [bool] + +Collect Thesaurus data and build Thesaurus Index. + + Default: 'thesaurus' => 1, + Example: 'thesaurus' => 0, + +=item B [bool] + +Build a summary of default From: header lines and list +remailers which allow overriding them. + + Default: 'fromlines' => 1, + Example: 'fromlines' => 0, + +=item B + +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 [seconds] + +How often to process incoming email. + + Default: 'processmail' => 60, # every minute + Example: 'processmail' => 5*60, # every 5 minutes + +=item B [seconds] + +How often to build mlist etc. + + Default: 'buildstats' => 5*60, # every 5 minutes + Example: 'buildstats' => 60*60, # hourly + +=item B [seconds] + +When building stats and we have chain pinging enabled +(see B), 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 [seconds] + +How often to build keyrings. + + Default: 'buildkeys' => 8*60*60, # every 8 hours + Example: 'buildkeys' => 24*60*60, # daily + +=item B [seconds] + +How often to update thesaurus index page. + + Default: 'buildthesaurus' => 60*60, # hourly + Example: 'buildthesaurus' => 24*60*60, # daily + +=item B [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 [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 [seconds] + +=item B [integer] + +How often to query remailers for new keys and configuration data +(remailer-xxx). Some requests are sent every B +seconds. The same request to the same remailer is sent only every +B 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 [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 [seconds] + +=item B [integer] + +How often to send pings. Pings are sent every B seconds. The +same remailer is pinged every B time pings are sent (This +means the same remailer is pinged every B * +B 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 [seconds] + +=item B [integer] + +=item B [integer] + +How often to send chain pings. Chain-Pings are sent every +B seconds. The same chain is pinged every +B time chain-pings are sent. Chains in +I (ic), that are chains that are either known or +believed to be bad or are not tested enough yet (see +B), should be tested more often: They are checked +every B 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 [integer] + +How many times to request remailer-xxx from a remailer (done every +B 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 [integer] + +How many times to request remailer-xxx from an assumed dead remailer (done every +B 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 [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 [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 [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 [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 [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 [seconds] + +After how long to expire chain pings. This should probably +be set to the same as B. + + Default: 'expire_chainpings' => 12*24*60*60, # 12 days + +=item B [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 [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 [seconds] + +How often to clean old files from the temp directory. + Default: 'cleanup_tmpdir' => 24*60*60, # daily + +=item B [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 [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 [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 + +The base directory of the Echolot installation. All other filenames and +directory names are local to this directory. B changes into this +directory upon startup. + + Default: The directory in which pingd is. + Example: 'homedir' => '/home/pinger/echolot', + +=item B + +The Maildir directory or Mbox which is searched for new messages. + + Default: 'mailin' => 'mail', + Example: 'mailin' => '/var/mail/echolot', + +=item B + +The Maildir directory where messages are put that could not be parsed. + + Default: 'mailerrordir' => 'mail-errors', + +=item B [bool] + +Whether to keep error messages at all + + Default: 'save_errormails' => 0, + Example: 'save_errormails' => 1, + +=item B + +The directory where statistics and keyrings are put. + + Default: 'resultdir' => 'results', + +=item B + +The directory where Thesaurus data is put. + + Default: 'thesaurusdir' => 'results/thesaurus', + +=item B + +The Thesaurus index file. + + Default: 'thesaurusindexfile' => 'results/thesaurus/index', + +=item B + +The From Lines index file. + + Default: 'fromlinesindexfile' => 'results/from', + +=item B + +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 + +The file to write the index.html to (relative to the result directory). + + Default: 'indexfilebasename' => 'echolot', + Example: 'indexfilebasename' => 'index', + +=item B + +The directory which is used as temporary GnuPG home for all keyring and +encryption/decryption actions. + + Default: 'gnupghome' => 'gnupghome', + +=item B + +Name of the GnuPG executable. If it is not in your PATH make sure to +include path information. + +If B is an empty string, the C default (usually B) +is used. + + Default: 'gnupg' => '', + Example: 'gnupg' => '/home/pinger/bin/myGnuPG', + +=item B + +Name of the gzip executable. If it is not in your PATH make sure to +include path information. + + Default: 'gzip' => 'gzip', + +=item B + +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 + +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 + +General purpose temp directory. Make sure it is not shared with other +applications. + + Default: 'tmpdir' => 'tmp', + +=item B + +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 + +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 + +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 + +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 + +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 + +File to write logs to. This file is reopened on SIGHUP. + + Default: 'logfile' => 'pingd.log', + Example: 'logfile' => '/var/log/echolot/pingd.log', + +=item B + +Minimum severity of messages to include in log file. Possible values are +B, B, B, B, B, B, B, B, and +B. + + Default: 'loglevel' => 'info', + Example: 'loglevel' => 'debug', + +=back + + +=head2 MISCELLANEOUS + +=over + +=item B [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 + +The extension that such metafiles (see above) should have. + + Default: 'meta_extension' => '.meta', + +=item B [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 is the top limit for the amount of bytes to add. The +actual number is randomly generated and uniformly distributed over +[0, B] + + Default: 'random_garbage' => '8192', + +=back + +=head2 CHAIN PINGING + +=over + +=item B [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 [bool] + +Show the results of our chainpinging in public stats. + + Default: 'show_chainpings' => 1, + +=item B + +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 + +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 [seconds] + +What time frame is taken into account when calculating chain stats. +This should probably be smaller than B. + + Default: 'chainping_period' => 12*24*60*60, # 12 days + +=item B [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 + +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 + +B determines which ping types are sent. +It is a hash that has the following keys: + +=over + +=item B + +Send out CPunk pings to CPunk remailers with their DSA key. + +=item B + +Send out CPunk pings to CPunk remailers with their RSA key. + +=item B + +Send out unencrypted pings to CPunk remailers that don't have pgponly +in their capsstring. + +=item B + +Pings mixmaster remailers. + +=back + + Default: 'do_pings' => { + 'cpunk-dsa' => 1, + 'cpunk-rsa' => 1, + 'cpunk-clear' => 1, + 'mix' => 1 + }, + +=item B + +B controls some aspects of chain pinging. +It's a hash over chaintypes - currently B and B. +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 + +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 + +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, +B, +B, +B, +B, +B, +B, +B, +B, +B, +B, and +B. + +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 + +Location of the CSS file. This is copied to resultdir/echolot.css. + + Default: 'echolot_css' => 'templates/echolot.css', + +=back + + + +=head2 STRINGS + +=over + +=item B + +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". + " 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 .\n", + +=back + +=head1 AUTHOR + +Peter Palfrader Epeter@palfrader.orgE + +=head1 BUGS + +Please report them at EURL:http://alioth.debian.org/projects/echolot/ + +=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 +# +# 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 B + +=item B B + +=item B B + +=item B B I
[I
...] + +=item B B I
[I
...] + +=item B B I
[I
...] + +=item B B I
[I
...] + +=item B B option=value [option=value..] I
[I
...] + +=item B B I + +=item B B I
+ +=item B B [I
[I
...]] + +=item B B [I
[I
...]] + +=item B B I
B<:>I
[I
B<:>I
...] + +=item B B + +=item B B + +=item B B + +=item B B + +=item B B + +=item B B + +=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 the ping daemon. + +=item B + +Send the running pingd process a SIGTERM. + +=item B + +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 I
[I
...] + +Add I
to the list of remailers to query for +keys and confs. + +=item B I
[I
...] + +Delete I
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 I
[I
...] + +Shorthand for B B B. This makes echolot +completely ignore that remailer, until you enable it again. + +=item B I
[I
...] + +Shorthand for B B B. + +=item B option=value [option=value..] I
[I
...] + +Possible options and values: + +=over + +=item B{B,B} + +Set B (show remailer in mlist, rlist etc.) for remailer I
to +either B or B. + +=item B{B,B} + +Set B (send out pings to that remailer) for remailer I
to +either B or B. + +=item B{B,B} + +Set B (fetch remailer-key and remailer-conf) for remailer I
to +either B or B. + +=back + +=item B I + +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"} = " mix middle";' + ./pingd setremailercaps '$remailer{"lcs"} = " mix klen1000";' + +=item B I
+ +Delete remailer-conf data for I
. The config data will be reset from +the next valid remailer-conf reply by the remailer. + +=item B [I
[I
...]] + +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 [I
[I
...]] + +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 I
B<:>I
[I
B<:>I
...] + +Send a command to immediatly send chain pings to the given chains. +A chain is two remailer addresses seperated by a colon. + +=item B + +Send a command to immediatly rebuild stats. + +=item B + +Send a command to immediatly rebuild the keyrings. + +=item B + +Send a command to immediatly rebuild the Thesaurus. + +=item B + +Send a command to immediatly rebuild the From Header lines page. + +=item B + +Print a status summary of all known addresses to the log (level notice). + +=item B + +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 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, B, B, +B, B, B, B, +B, B, B, B, +or B 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 command. + +Read and process the commands file on startup. + +=item B<--detach> + +Usefull only with the B command. + +Tell B to detach. + +=back + +=head1 BASE DIRECTORY + +The home directory to which everything else is relative. + +Basedir defaults to whatever directory the B binary is located. It can +be overridden by the B environment variable which in turn is +weaker than the B<--basedir> setting. + +This directory is then used to locate the configuration file B (see +FILES below). + +The B setting in B 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 environment variable + +=item /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, B, and B B 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 will execute any pending commands from the commands file +(B 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 Epeter@palfrader.orgE + +=head1 BUGS + +Please report them at EURL:http://alioth.debian.org/projects/echolot/ + +=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
\n") unless scalar @argv; + my @addresses; + for my $address (@argv) { + die ("argument
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 = ; + 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 = ; + 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 \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 \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 , "{'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 +# +# 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 + +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 @@ + + + Remailers (combined list) [<TMPL_VAR NAME="SITE_NAME">] + "> + + + + +

Remailer Reliability Stats []

+ +

+Up Plaintext version + +

+Available Stats: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cypherpunk (Type I)Cypherpunk (Type I)Mixmaster (Type II)Combined
v1(rsa)(dsa)(cleartext)v1V2
v2(rsa)(dsa)(cleartext)v2
+ +

Remailers (combined list)

+ +

+This is an automatically generated list of remailer reliability +statistics. +Please see the Legend below for interpretative data. + +

+

+
+
+ + + +
+Created by Echolot .
+Last update: . + + + + + 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 @@ + + + Remailer Reliability Stats [<TMPL_VAR NAME="SITE_NAME">] + "> + + + + + +

Remailer Reliability Stats []

+ +

+This pinger automatically collects and generates remailer reliability statistics +for both Type I (Cypherpunk) and Type II (Mixmaster) remailers. It also +offers current keyrings and each remailer's reply to remailer-xxx +queries for your convenience. + +

Available Stats

+ +

+Out of the Cypherpunk and + Mixmaster remailers +( unique addresses) on these stats there +are CPunks and Mixes +over 98.0% in terms of overall reliability. + +

HTML

+ + + + + + + + + + + + + + + + + + + +
Cypherpunk (Type I)Mixmaster (Type II)Combined
rlist.html + + + (rsa, + dsa, + unencrypted) + + + mlist.htmlclist2.html
rlist2.html + + + (rsa, + dsa, + unencrypted) + + + mlist2.html
+ +

Plain Text

+ + + + + + + + + + + + + + + + + + + +
Cypherpunk (Type I)Mixmaster (Type II)Combined
rlist.txt + + + (rsa, + dsa, + unencrypted) + + + mlist.txtclist2.txt
rlist2.txt + + + (rsa, + dsa, + unencrypted) + + + mlist2.txt
+ + +

Available Keyrings

+ + + + + + + + + + + + + +
Cypherpunk (Type I)Mixmaster (Type II)
RSA onlypubring.mix
RSA and DSAtype2.list
+ + +

Thesaurus

+ +Find the Thesaurus index in thesaurus/. +
+ + +

From Header Lines

+ +A summary of From Headers each remailer uses has also +been compiled. It also lists which remailers allow setting the From: Header. +
+ +
+Created by Echolot at (individual pages +may be newer).
+This pinger is operated by .
+All times in Coordinated Universal Time (UTC). + + + + + 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 @@ + + + From Headers [<TMPL_VAR NAME="SITE_NAME">] + "> + + + + +

From Headers []

+ +

+Up + +

Default From: Headers

+ +

+The default From headers of all remailers when sending e-mail. Middleman +remailers are not listed here. + +

+ + + + + + + + + + + + + + +
nickDisclaimerFrom Linetype
top bot
+ +

User Supplied From: Headers

+ +

+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. + +

+ + + + + + + + + + + + + + + +
nickDisclaimerFrom Linetype
top bot
+ +


+Created by Echolot .
+Last update: . + + + + + 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 @@ + + + Mixmaster Remailers [<TMPL_VAR NAME="SITE_NAME">] + "> + + + + +

Remailer Reliability Stats []

+ +

+Up Plaintext version + +

+Available Stats: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cypherpunk (Type I)Cypherpunk (Type I)Mixmaster (Type II)Combined
v1(rsa)(dsa)(cleartext)v1V2
v2(rsa)(dsa)(cleartext)v2
+ +

Mixmaster Remailers

+ +

+This is an automatically generated list of remailer reliability +statistics. +Please see the Legend below for interpretative data. + +

+

+
+
+ + + +
+Created by Echolot .
+Last update: . + + + + + 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 @@ + + + Mixmaster Remailers (v2) [<TMPL_VAR NAME="SITE_NAME">] + "> + + + + +

Remailer Reliability Stats []

+ +

+Up Plaintext version + +

+Available Stats: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cypherpunk (Type I)Cypherpunk (Type I)Mixmaster (Type II)Combined
v1(rsa)(dsa)(cleartext)v1V2
v2(rsa)(dsa)(cleartext)v2
+ +

Mixmaster Remailers (v2)

+ +

+This is an automatically generated list of remailer reliability +statistics. +Please see the Legend below for interpretative data. + +

+

+
+
+ + + +
+Created by Echolot .
+Last update: . + + + + + 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 @@ + + + Cypherpunk Remailers (cleartext only) [<TMPL_VAR NAME="SITE_NAME">] + "> + + + + +

Remailer Reliability Stats []

+ +

+Up Plaintext version + +

+Available Stats: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cypherpunk (Type I)Cypherpunk (Type I)Mixmaster (Type II)Combined
v1(rsa)(dsa)(cleartext)v1V2
v2(rsa)(dsa)(cleartext)v2
+ +

Cypherpunk Remailers (cleartext only)

+ +

+This is an automatically generated list of remailer reliability +statistics. +Please see the Legend below for interpretative data. + +

+

+
+
+ + + +
+Created by Echolot .
+Last update: . + + + + + 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 @@ + + + Cypherpunk Remailers (DSA only) [<TMPL_VAR NAME="SITE_NAME">] + "> + + + + +

Remailer Reliability Stats []

+ +

+Up Plaintext version + +

+Available Stats: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cypherpunk (Type I)Cypherpunk (Type I)Mixmaster (Type II)Combined
v1(rsa)(dsa)(cleartext)v1V2
v2(rsa)(dsa)(cleartext)v2
+ +

Cypherpunk Remailers (DSA only)

+ +

+This is an automatically generated list of remailer reliability +statistics. +Please see the Legend below for interpretative data. + +

+

+
+
+ + + +
+Created by Echolot .
+Last update: . + + + + + 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 @@ + + + Cypherpunk Remailers (RSA only) [<TMPL_VAR NAME="SITE_NAME">] + "> + + + + +

Remailer Reliability Stats []

+ +

+Up Plaintext version + +

+Available Stats: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cypherpunk (Type I)Cypherpunk (Type I)Mixmaster (Type II)Combined
v1(rsa)(dsa)(cleartext)v1V2
v2(rsa)(dsa)(cleartext)v2
+ +

Cypherpunk Remailers (RSA only)

+ +

+This is an automatically generated list of remailer reliability +statistics. +Please see the Legend below for interpretative data. + +

+

+
+
+ + + +
+Created by Echolot .
+Last update: . + + + + + 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 @@ + + + Cypherpunk Remailers [<TMPL_VAR NAME="SITE_NAME">] + "> + + + + +

Remailer Reliability Stats []

+ +

+Up Plaintext version + +

+Available Stats: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cypherpunk (Type I)Cypherpunk (Type I)Mixmaster (Type II)Combined
v1(rsa)(dsa)(cleartext)v1V2
v2(rsa)(dsa)(cleartext)v2
+ +

Cypherpunk Remailers

+ +

+This is an automatically generated list of remailer reliability +statistics. +Please see the Legend below for interpretative data. + +

+

+
+
+ + + +
+Created by Echolot .
+Last update: . + + + + + 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 @@ + + + Cypherpunk Remailers (v2, cleartext only) [<TMPL_VAR NAME="SITE_NAME">] + "> + + + + +

Remailer Reliability Stats []

+ +

+Up Plaintext version + +

+Available Stats: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cypherpunk (Type I)Cypherpunk (Type I)Mixmaster (Type II)Combined
v1(rsa)(dsa)(cleartext)v1V2
v2(rsa)(dsa)(cleartext)v2
+ +

Cypherpunk Remailers (v2, cleartext only)

+ +

+This is an automatically generated list of remailer reliability +statistics. +Please see the Legend below for interpretative data. + +

+

+
+
+ + + +
+Created by Echolot .
+Last update: . + + + + + 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 @@ + + + Cypherpunk Remailers (v2, DSA only) [<TMPL_VAR NAME="SITE_NAME">] + "> + + + + +

Remailer Reliability Stats []

+ +

+Up Plaintext version + +

+Available Stats: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cypherpunk (Type I)Cypherpunk (Type I)Mixmaster (Type II)Combined
v1(rsa)(dsa)(cleartext)v1V2
v2(rsa)(dsa)(cleartext)v2
+ +

Cypherpunk Remailers (v2, DSA only)

+ +

+This is an automatically generated list of remailer reliability +statistics. +Please see the Legend below for interpretative data. + +

+

+
+
+ + + +
+Created by Echolot .
+Last update: . + + + + + 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 @@ + + + Cypherpunk Remailers (v2, RSA only) [<TMPL_VAR NAME="SITE_NAME">] + "> + + + + +

Remailer Reliability Stats []

+ +

+Up Plaintext version + +

+Available Stats: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cypherpunk (Type I)Cypherpunk (Type I)Mixmaster (Type II)Combined
v1(rsa)(dsa)(cleartext)v1V2
v2(rsa)(dsa)(cleartext)v2
+ +

Cypherpunk Remailers (v2, RSA only)

+ +

+This is an automatically generated list of remailer reliability +statistics. +Please see the Legend below for interpretative data. + +

+

+
+
+ + + +
+Created by Echolot .
+Last update: . + + + + + 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 @@ + + + Cypherpunk Remailers (v2) [<TMPL_VAR NAME="SITE_NAME">] + "> + + + + +

Remailer Reliability Stats []

+ +

+Up Plaintext version + +

+Available Stats: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cypherpunk (Type I)Cypherpunk (Type I)Mixmaster (Type II)Combined
v1(rsa)(dsa)(cleartext)v1V2
v2(rsa)(dsa)(cleartext)v2
+ +

Cypherpunk Remailers (v2)

+ +

+This is an automatically generated list of remailer reliability +statistics. +Please see the Legend below for interpretative data. + +

+

+
+
+ + + +
+Created by Echolot .
+Last update: . + + + + + 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 @@ + + + Thesaurus [<TMPL_VAR NAME="SITE_NAME">] + "> + + + + +

Thesaurus []

+ +

+Up + +

+ + + + + + + + + + + + + + + + +
nickAddressconfhelpkeystatsadminkey
">
N/A
">
N/A
">
N/A
">
N/A
">
N/A
+ +


+Created by Echolot .
+Last update: . + + + + + 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 @@ +

Legend

+ +

+History: The result of test messages sent in the last 12 days. +

+

+ + + + + + + + + +
?No test message sent
(space)Response not received
#Response in less than 5 minutes
*Response in less than 1 hour
+Response in less than 4 hours
-Response in less than 24 hours
.Response in less than 2 days
_Response in more than 2 days
+
+

+Latency: The average response time of the remailer. +

+Uptime: 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 @@ +

Legend

+ +

+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. + +

+Latent-Hist shows the average default response time for each day: +

+ + + + + + + + + + + + + + +
0less than 20 minutes
1less than 1 hour
...
9less than 9 hours
Aless than 12 hours
Bless than 18 hours
Cless than 24 hours
...
Gless than 48 hours
Hmore than 48 hours
?No responses received / No data
+
+ +

+Latent shows the average default response time in HH:MM format. + +

+Uptime-Hist shows the Uptime percentage (responses received divided by pings +sent) for each day: +

+ + + + + + + + +
+100% (Responses were received for all pings sent)
990-99.9% (About 9 responses were received for every 10 pings)
...
110-19.9%
00-9.9%
?No pings sent / No data
+
+ +

+Uptime shows the average Uptime percentage for 12 days. + +

+Options shows an abbreviated form of the strings listed in the +Remailer-Capabilities section above: +

+ + + + + + + + + + + + + + + + + + + +
Dmiddle (Remailer is middleman and chains to other remailers)
Ppost (Supports news posting (Anon-Post-To or Post)
M/R/2mix/remix/remix2 (Supported Mixmaster features)
Hhybrid (Supports CPunk directives in Mix messages)
G/2repgp/repgp2
Opgponly (Requires Cypherpunk messages to be PGP encrypted)
Xext (Supports extended directive features)
Amax (Supports Max-Size, Max-Count, and Max-Date directives)
Ttest (Supports the Test-To directive)
Llatent (Supports the Latent-Time directive)
e/Eek/ekx (Supports Encrypt-Key/-3DES,-CAST directives)
Uesub (Supports the Encrypt-Subject directive)
Iinflt (Supports the Inflate directive)
Nrhop (Supports the Rand-Hop directive)
#klen - The digit indicates the maximum message size: +
+ + + + + + + +
9Max is greater than 900K
8Max is less than 900K
...
1Max is less than 200K
0Max is less than 100K
+
+
+
+ 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 +# +# 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 +# +# 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 +# +# 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: -- cgit v1.2.3