From ecd052098413f87701ba00e28f88563248a177f6 Mon Sep 17 00:00:00 2001 From: Peter Palfrader Date: Wed, 5 Jun 2002 04:05:40 +0000 Subject: Initial Import --- .cvsignore | 2 + COPYING | 340 +++++++++++++++++++++++++++++++++++ Echolot/Conf.pm | 84 +++++++++ Echolot/Config.pm | 48 +++++ Echolot/Globals.pm | 37 ++++ Echolot/Mailin.pm | 120 +++++++++++++ Echolot/Scheduler.pm | 150 ++++++++++++++++ Echolot/Storage/File.pm | 458 ++++++++++++++++++++++++++++++++++++++++++++++++ Echolot/Tools.pm | 93 ++++++++++ LICENSE | 15 ++ README | 28 +++ pingd | 40 +++++ pingd.conf | 24 +++ 13 files changed, 1439 insertions(+) create mode 100644 .cvsignore create mode 100644 COPYING create mode 100644 Echolot/Conf.pm create mode 100644 Echolot/Config.pm create mode 100644 Echolot/Globals.pm create mode 100644 Echolot/Mailin.pm create mode 100644 Echolot/Scheduler.pm create mode 100644 Echolot/Storage/File.pm create mode 100644 Echolot/Tools.pm create mode 100644 LICENSE create mode 100644 README create mode 100755 pingd create mode 100644 pingd.conf diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..4bda8ff --- /dev/null +++ b/.cvsignore @@ -0,0 +1,2 @@ +data +mail diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d60c31a --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 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/Echolot/Conf.pm b/Echolot/Conf.pm new file mode 100644 index 0000000..b19b25c --- /dev/null +++ b/Echolot/Conf.pm @@ -0,0 +1,84 @@ +package Echolot::Conf; + +# (c) 2002 Peter Palfrader +# $Id: Conf.pm,v 1.1 2002/06/05 04:05:40 weasel Exp $ +# + +=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. + +=cut + +use strict; +use warnings; +use Carp qw{cluck}; + + +sub send_requests() { + Echolot::Globals::get()->{'storage'}->delay_commit(); + for my $remailer (Echolot::Globals::get()->{'storage'}->get_addresses()) { + next unless ($remailer->{'status'} eq 'active'); + for my $type (qw{conf key help stats}) { + Echolot::Tools::send_message( + 'To' => $remailer->{'address'}, + 'Subject' => 'remailer-'.$type, + 'Token' => $type.'.'.$remailer->{'id'}) + }; + Echolot::Globals::get()->{'storage'}->decrease_ttl($remailer->{'address'}); + }; + Echolot::Globals::get()->{'storage'}->enable_commit(); +}; + +sub remailer_conf($$$) { + my ($conf, $token, $time) = @_; + + my ($id) = $token =~ /^conf\.(\d+)$/; + cluck("Could not find id in token '$token'"), return 0 unless defined $id; + my ($remailer_type) = ($conf =~ /^\s*Remailer-Type:\s* (.*?) \s*$/imx); + cluck("No remailer type found in remailer_conf from '$token'"), return 0 unless defined $remailer_type; + my ($remailer_caps) = ($conf =~ /^\s*( \$remailer{".*"} \s*=\s* "<.*@.*>.*"; )\s*$/imx); + cluck("No remailer caps found in remailer_conf from '$token'"), return 0 unless defined $remailer_caps; + my ($remailer_nick, $remailer_address) = ($remailer_caps =~ /^\s* \$remailer{"(.*)"} \s*=\s* "<(.*@.*)>.*"; \s*$/ix); + cluck("No remailer nick found in remailer_caps from '$token': '$remailer_caps'"), return 0 unless defined $remailer_nick; + cluck("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); + if ($remailer->{'address'} ne $remailer_address) { + # Address mismatch -> Ignore reply and add $remailer_address to prospective addresses + cluck("Remailer address mismatch $remailer->{'address'} vs $remailer_address. Adding latter to prospective remailers."); + Echolot::Globals::get()->{'storage'}->add_prospective_address($remailer_address, 'conf-reply'); + } else { + Echolot::Globals::get()->{'storage'}->restore_ttl( $remailer->{'address'} ); + Echolot::Globals::get()->{'storage'}->set_caps($remailer_type, $remailer_caps, $remailer_nick, $remailer_address, $time); + } +}; + +sub remailer_key($$$) { + my ($conf, $token, $time) = @_; + + print "Remailer key\n"; +}; + +sub remailer_stats($$$) { + my ($conf, $token, $time) = @_; + + #print "Remailer stats\n"; +}; + +sub remailer_help($$$) { + my ($conf, $token, $time) = @_; + + #print "Remailer help\n"; +}; + +1; +# vim: set ts=4 shiftwidth=4: diff --git a/Echolot/Config.pm b/Echolot/Config.pm new file mode 100644 index 0000000..f679af8 --- /dev/null +++ b/Echolot/Config.pm @@ -0,0 +1,48 @@ +package Echolot::Config; + +# (c) 2002 Peter Palfrader +# $Id: Config.pm,v 1.1 2002/06/05 04:05:40 weasel Exp $ +# + +=pod + +=head1 Name + +Echolot::Config - echolot configuration + +=head1 DESCRIPTION + +=cut + +use strict; +use warnings; +use XML::Parser; +use XML::Dumper; +use Carp; + +my $CONFIG; + +sub init() { + my $DEFAULT; + $DEFAULT->{'recipient_delimiter'} = '+'; + $DEFAULT->{'dev_random'} = '/dev/random'; + $DEFAULT->{'hash_len'} = 8; + + { + my $parser = new XML::Parser(Style => 'Tree'); + my $tree = $parser->parsefile('pingd.conf'); + my $dump = new XML::Dumper; + $CONFIG = $dump->xml2pl($tree); + } + + for my $key (keys %$DEFAULT) { + $CONFIG->{$key} = $DEFAULT->{$key} unless defined $CONFIG->{$key}; + }; +}; + +sub get() { + return $CONFIG; +}; + +1; +# vim: set ts=4 shiftwidth=4: diff --git a/Echolot/Globals.pm b/Echolot/Globals.pm new file mode 100644 index 0000000..4b5eb13 --- /dev/null +++ b/Echolot/Globals.pm @@ -0,0 +1,37 @@ +package Echolot::Globals; + +# (c) 2002 Peter Palfrader +# $Id: Globals.pm,v 1.1 2002/06/05 04:05:40 weasel Exp $ +# + +=pod + +=head1 Name + +Echolot::Globals - echolot global variables + +=head1 DESCRIPTION + +=cut + +use strict; +use warnings; +use Carp; + +my $GLOBALS; + +sub init { + my $hostname = `hostname`; + $hostname =~ /^([a-zA-Z0-9_-]*)$/; + $hostname = $1 || 'unknown'; + $GLOBALS->{'hostname'} = $hostname; + $GLOBALS->{'storage'} = new Echolot::Storage::File ( datadir => Echolot::Config::get()->{'storage'}->{'File'}->{'basedir'} ); + $GLOBALS->{'internalcounter'} = 1; +}; + +sub get() { + return $GLOBALS; +}; + +1; +# vim: set ts=4 shiftwidth=4: diff --git a/Echolot/Mailin.pm b/Echolot/Mailin.pm new file mode 100644 index 0000000..411433a --- /dev/null +++ b/Echolot/Mailin.pm @@ -0,0 +1,120 @@ +package Echolot::Mailin; + +# (c) 2002 Peter Palfrader +# $Id: Mailin.pm,v 1.1 2002/06/05 04:05:40 weasel Exp $ +# + +=pod + +=head1 Name + +Echolot::Mailin - Incoming Mail Dispatcher for Echolot + +=head1 DESCRIPTION + + +=cut + +use strict; +use warnings; +use Carp qw{cluck}; +use English; +use Echolot::Globals; + +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 + cluck("Cannot link $from to $to: $! - Trying move"), + rename($from, $to) or + cluck("Renaming $from to $to didn't work either: $!"), + return 0; + + $link_success && (unlink($from) or + cluck("Cannot unlink $from: $!") ); + return 1; +}; + +sub handle($) { + my ($file) = @_; + + open (FH, $file) or + cluck("Cannot open file $file: $!"), + return 0; + + my $to; + while () { + chomp; + last if $_ eq ''; + + if (m/^To:\s*(.*?)\s*$/) { + $to = $1; + }; + }; + my $body = join('', ); + close (FH) or + cluck("Cannot close file $file: $!"); + + (defined $to) or + cluck("No To header found in $file"), + return 0; + + my $delimiter = quotemeta( Echolot::Config::get()->{'recipient_delimiter'}); + my ($type, $timestamp, $received_hash) = $to =~ /$delimiter (.*) = (\d+) = ([0-9a-f]+) @/x or + cluck("Could not parse to header '$to'"), + return 0; + + 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 + cluck("Hash mismatch in '$to'"), + return 0; + + 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::Ping::receive($body, $type, $timestamp), return 1 if ($type =~ /^ping\./); + + cluck("Didn't know what to do with '$to'"), + return 0; +}; + +sub process() { + my $mailindir = Echolot::Config::get()->{'mailindir'}; + my $targetdir = Echolot::Config::get()->{'mailerrordir'}; + my @files = (); + for my $sub (qw{new cur}) { + opendir(DIR, $mailindir.'/'.$sub) or + cluck("Cannot open direcotry '$mailindir/$sub': $!"), + return 0; + push @files, map { $sub.'/'.$_ } grep { ! /^\./ } readdir(DIR); + closedir(DIR) or + cluck("Cannot close direcotry '$mailindir/$sub': $!"); + }; + for my $file (@files) { + $file =~ /^(.*)$/s or + croak("I really should match here. ('$file')."); + $file = $1; + if (handle($mailindir.'/'.$file)) { + unlink($mailindir.'/'.$file); + } else { + my $name = make_sane_name(); + sane_move($mailindir.'/'.$file, $targetdir.'/new/'.$name) or + cluck("Sane moving of $mailindir/$file to $targetdir/new/$name failed"); + }; + }; +}; + +1; + +# vim: set ts=4 shiftwidth=4: diff --git a/Echolot/Scheduler.pm b/Echolot/Scheduler.pm new file mode 100644 index 0000000..24ca6e3 --- /dev/null +++ b/Echolot/Scheduler.pm @@ -0,0 +1,150 @@ +package Echolot::Scheduler; + +# (c) 2002 Peter Palfrader +# $Id: Scheduler.pm,v 1.1 2002/06/05 04:05:40 weasel Exp $ +# + +=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 warnings; +use Carp gw{cluck}; + +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) + +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. + +=cut +sub add($$$$$) { + my ($self, $name, $interval, $offset, $what) = @_; + + if (defined $self->{'tasks'}->{$name}) { + @{ $self->{'schedule'} } = grep { $_->{'name'} ne $name } @{ $self->{'schedule'} }; + }; + + $self->{'tasks'}->{$name} = + { + interval => $interval, + offset => $offset, + what => $what, + order => $ORDER++ + }; + + $self->schedule($name); + + return 1; +}; + +=item B (I, I) + +Internal function. + +Schedule execution of I for I. If I is not given it is calculated +from I and I passed to B. + +=cut +sub schedule($$;$) { + my ($self, $name, $for) = @_; + + (defined $self->{'tasks'}->{$name}) or + cluck("Task $name is not defined"), + return 0; + + my $interval = $self->{'tasks'}->{$name}->{'interval'}; + my $offset = $self->{'tasks'}->{$name}->{'offset'}; + + + unless (defined $for) { + my $now = time(); + $for = $now - $now % $interval + $offset; + ($for <= $now) and $for += $interval; + }; + + push @{ $self->{'schedule'} }, + { + start => $for, + order => $self->{'tasks'}->{$name}->{'order'}, + name => $name + }; + + @{ $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) = @_; + + my $task = shift @{ $self->{'schedule'} }; + (defined $task) or + croak("Scheduler is empty"), + return 0; + + while(1) { + my $now = time(); + if ($task->{'start'} < $now) { + warn("Task $task->{'name'} could not be started on time\n"); + } else { + sleep ($task->{'start'} - $now); + }; + + $now = $task->{'start'}; + do { + my $name = $task->{'name'}; + (defined $self->{'tasks'}->{$name}) or + warn("Task $task->{'name'} is not defined\n"); + + my $what = $self->{'tasks'}->{$name}->{'what'}; + last if ($what eq 'exit'); + &$what(); + $self->schedule($name, $now + $self->{'tasks'}->{$name}->{'interval'}); + + $task = shift @{ $self->{'schedule'} }; + (defined $task) or + croak("Scheduler is empty"), + return 0; + } while ($now == $task->{'start'}); + }; + + return 1; +}; + +# vim: set ts=4 shiftwidth=4: diff --git a/Echolot/Storage/File.pm b/Echolot/Storage/File.pm new file mode 100644 index 0000000..6e66ec8 --- /dev/null +++ b/Echolot/Storage/File.pm @@ -0,0 +1,458 @@ +package Echolot::Storage::File; + +# (c) 2002 Peter Palfrader +# $Id: File.pm,v 1.1 2002/06/05 04:05:40 weasel Exp $ +# + +=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 warnings; +use XML::Parser; +use XML::Dumper; +use IO::Handle; +use English; +use Carp qw{cluck confess}; +use Fcntl ':flock'; # import LOCK_* constants +use Fcntl ':seek'; # import LOCK_* constants +use Echolot::Tools; + +=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 + +my $CONSTANTS = { + 'metadatafile' => 'metadata' +}; + +$ENV{'PATH'} = '/bin:/usr/bin'; +delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; + +my $METADATA_VERSION = 1; + +my $INTERNAL_COUNT = 1; + +sub new { + my ($class, %params) = @_; + my $self = {}; + bless $self, $class; + + 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 + cluck ('Opening Metadata failed. Exiting'), + exit 1; + $self->metadata_read() or + cluck ('Reading Metadata from Storage failed. Exiting'), + exit 1; + $self->pingdata_open() or + cluck ('Opening Ping files failed. Exiting'), + exit 1; + $self->enable_commit(); + + return $self; +}; + +sub commit($) { + my ($self) = @_; + + return if $self->{'DELAY_COMMIT'}; + $self->metadata_write(); +}; + +sub delay_commit($) { + my ($self) = @_; + + $self->{'DELAY_COMMIT'}++; +}; +sub enable_commit($) { + my ($self) = @_; + + $self->{'DELAY_COMMIT'}--; + $self->commit(); +}; + +sub finish($) { + my ($self) = @_; + + $self->pingdata_close(); + $self->metadata_write(); + $self->metadata_close(); +}; + + +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 + cluck("Cannot open $filename for reading: $!"), + return 0; + } else { + open($self->{'METADATA_FH'}, '+>' . $filename) or + cluck("Cannot open $filename for reading: $!"), + return 0; + }; + flock($self->{'METADATA_FH'}, LOCK_SH) or + cluck("Cannot get shared lock on $filename: $!"), + return 0; +}; + +sub metadata_close($) { + my ($self) = @_; + + flock($self->{'METADATA_FH'}, LOCK_UN) or + cluck("Error when releasing lock on metadata file: $!"), + return -1; + close($self->{'METADATA_FH'}) or + cluck("Error when closing metadata file: $!"), + return 0; +}; + + +sub metadata_read($) { + my ($self) = @_; + + $self->{'METADATA'} = (); + seek($self->{'METADATA_FH'}, 0, SEEK_SET) or + cluck("Cannot seek to start of metadata file: $!"), + return 0; + eval { + my $parser = new XML::Parser(Style => 'Tree'); + my $tree = $parser->parse( $self->{'METADATA_FH'} ); + my $dump = new XML::Dumper; + $self->{'METADATA'} = $dump->xml2pl($tree); + }; + $EVAL_ERROR and + cluck("Error when reading from metadata file: $EVAL_ERROR"), + return 0; + + defined($self->{'METADATA'}->{'version'}) or + cluck("Stored data lacks version header"), + return 0; + ($self->{'METADATA'}->{'version'} == ($METADATA_VERSION)) or + cluck("Metadata version mismatch ($self->{'METADATA'}->{'version'} vs. $METADATA_VERSION)"), + return 0; + + + defined($self->{'METADATA'}->{'secret'}) or + $self->{'METADATA'}->{'secret'} = Echolot::Tools::make_random ( 16, armor => 1 ), + $self->commit(); + + return 1; +}; + +sub metadata_write($) { + my ($self) = @_; + + my $dump = new XML::Dumper; + my $data = $dump->pl2xml($self->{'METADATA'}); + my $fh = $self->{'METADATA_FH'}; + + seek($fh, 0, SEEK_SET) or + cluck("Cannot seek to start of metadata file: $!"), + return 0; + truncate($fh, 0) or + cluck("Cannot truncate metadata file to zero length: $!"), + return 0; + print($fh "\n") or + cluck("Error when writing to metadata file: $!"), + return 0; + print($fh $data) or + cluck("Error when writing to metadata file: $!"), + return 0; + + return 1; +}; + + +sub pingdata_open($) { + my ($self) = @_; + + for my $remailer_name ( keys %{$self->{'METADATA'}->{'remailers'}} ) { + for my $key ( keys %{$self->{'METADATA'}->{'remailers'}->{$remailer_name}->{'keys'}} ) { + my $basename = $self->{'METADATA'}->{'remailers'}->{$remailer_name}->{'stats'}->{$key}; + defined($basename) or + $basename = $self->{'METADATA'}->{'remailers'}->{$remailer_name}->{'stats'}->{$key} = $remailer_name.'.'.$key.'.'.time.'.'.$PROCESS_ID.'_'.$INTERNAL_COUNT++, + $self->commit(); + + my $filename = $self->{'datadir'} .'/'. $basename; + + for my $type ('out', 'done') { + my $fh = new IO::Handle; + if ( -e $filename.'.'.$type ) { + open($fh, '+<' . $filename.'.'.$type) or + cluck("Cannot open $filename.$type for reading: $!"), + return 0; + $self->{'PING_FHS'}->{$remailer_name}->{$key}->{$type} = $fh; + } else { + open($fh, '+>' . $filename.'.'.$type) or + cluck("Cannot open $filename.$type for reading: $!"), + return 0; + $self->{'PING_FHS'}->{$remailer_name}->{$key}->{$type} = $fh; + }; + flock($fh, LOCK_EX) or + cluck("Cannot get exclusive lock on $remailer_name $type pings: $!"), + return 0; + }; + }; + }; + return 1; +}; + +sub get_pings($$$$) { + my ($self, $remailer_name, $key, $type) = @_; + + defined ($self->{'METADATA'}->{'remailers'}->{$remailer_name}) or + cluck ("$remailer_name does not exist in Metadata"), + return 0; + + my @pings; + my $fh = $self->{'PING_FHS'}->{$remailer_name}->{$key}->{$type}; + + defined ($fh) or + cluck ("$remailer_name; key=$key has no assigned filehandle for $type pings"), + return 0; + + seek($fh, 0, SEEK_SET) or + cluck("Cannot seek to start of $remailer_name $type pings: $!"), + return 0; + + if ($type eq 'out') { + @pings = map {chomp; $_; } <$fh>; + } elsif ($type eq 'done') { + @pings = map {chomp; my @arr = split (/\s+/, $_, 2); \@arr; } <$fh>; + } else { + confess("What the hell am I doing here? $remailer_name; $key; $type"), + return 0; + }; + return \@pings; +}; + +sub pingdata_close() { + my ($self) = @_; + + for my $remailer_name ( keys %{$self->{'PING_FHS'}} ) { + for my $key ( keys %{$self->{'PING_FHS'}->{$remailer_name}} ) { + for my $type ('out', 'done') { + + my $fh = $self->{'PING_FHS'}->{$remailer_name}->{$key}->{$type}; + flock($fh, LOCK_UN) or + cluck("Error when releasing lock on $remailer_name $type pings: $!"), + return 0; + close ($self->{'PING_FHS'}->{$remailer_name}->{$key}->{$type}) or + cluck("Error when closing $remailer_name $type pings: $!"), + return 0; + }; + }; + }; + return 1; +}; + + + + + +sub register_pingout($$$$) { + my ($self, $remailer_name, $key, $sent_time) = @_; + + defined ($self->{'METADATA'}->{'remailers'}->{$remailer_name}) or + cluck ("$remailer_name does not exist in Metadata"), + return 0; + + my $fh = $self->{'PING_FHS'}->{$remailer_name}->{$key}->{'out'}; + defined ($fh) or + cluck ("$remailer_name; key=$key has no assigned filehandle for outgoing pings"), + return 0; + seek($fh, 0, SEEK_END) or + cluck("Cannot seek to end of $remailer_name out pings: $!"), + return 0; + print($fh $sent_time."\n") or + cluck("Error when writing to $remailer_name out pings: $!"), + return 0; + + return 1; +}; + +sub register_pingdone($$$$$) { + my ($self, $remailer_name, $key, $sent_time, $latency) = @_; + + defined ($self->{'METADATA'}->{'remailers'}->{$remailer_name}) or + cluck ("$remailer_name does not exist in Metadata"), + return 0; + + my $outpings = $self->get_pings($remailer_name, $key, 'out'); + my $origlen = scalar (@$outpings); + @$outpings = grep { $_ != $sent_time } @$outpings; + ($origlen == scalar (@$outpings)) and + warn("No ping outstanding for $remailer_name, $key, $sent_time\n"), + return 1; + + # write ping to done + my $fh = $self->{'PING_FHS'}->{$remailer_name}->{$key}->{'done'}; + defined ($fh) or + cluck ("$remailer_name; key=$key has no assigned filehandle for done pings"), + return 0; + seek($fh, 0, SEEK_END) or + cluck("Cannot seek to end of $remailer_name out pings: $!"), + return 0; + print($fh $sent_time." ".$latency."\n") or + cluck("Error when writing to $remailer_name out pings: $!"), + return 0; + + # rewrite outstanding pings + $fh = $self->{'PING_FHS'}->{$remailer_name}->{$key}->{'out'}; + defined ($fh) or + cluck ("$remailer_name; key=$key has no assigned filehandle for out pings"), + return 0; + seek($fh, 0, SEEK_SET) or + cluck("Cannot seek to start of outgoing pings file for remailer $remailer_name; key=$key: $!"), + return 0; + truncate($fh, 0) or + cluck("Cannot truncate outgoing pings file for remailer $remailer_name; key=$key file to zero length: $!"), + return 0; + print($fh (join "\n", @$outpings),"\n") or + cluck("Error when writing to outgoing pings file for remailer $remailer_name; key=$key file: $!"), + return 0; + + return 1; +}; + +sub add_prospective_address($$$) { + my ($self, $addr, $where) = @_; + + push @{ $self->{'METADATA'}->{'prostective_addresses'} }, + { 'address' => $addr, + 'where' => $where }; + $self->commit(); +}; + +sub get_addresses($) { + my ($self) = @_; + + my @addresses = keys %{$self->{'METADATA'}->{'addresses'}}; + my @return_data = map { my %tmp = %{$self->{'METADATA'}->{'addresses'}->{$_}}; $tmp{'address'} = $_; \%tmp; } @addresses; + return @return_data; +}; + +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) { + 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; +}; + +sub decrease_ttl($$) { + my ($self, $address) = @_; + + defined ($self->{'METADATA'}->{'addresses'}->{$address}) or + cluck ("$address does not exist in Metadata address list"), + return 0; + $self->{'METADATA'}->{'addresses'}->{$address}->{'ttl'} --; + $self->{'METADATA'}->{'addresses'}->{$address}->{'status'} = 'disabled' + if ($self->{'METADATA'}->{'addresses'}->{$address}->{'ttl'} <= 0); + $self->commit(); + return 1; +}; + +sub restore_ttl($$) { + my ($self, $address) = @_; + + defined ($self->{'METADATA'}->{'addresses'}->{$address}) or + cluck ("$address does not exist in Metadata address list"), + return 0; + $self->{'METADATA'}->{'addresses'}->{$address}->{'ttl'} = Echolot::Config::get()->{'addresses_default_ttl'}; + $self->commit(); + return 1; +}; + +sub set_caps($$$$$$) { + my ($self, $type, $caps, $nick, $address, $timestamp) = @_; + + if (! defined $self->{'METADATA'}->{'remailers'}->{$address}) { + $self->{'METADATA'}->{'remailers'}->{$address} = + { + status => 'active', + pingit => Echolot::Config::get()->{'ping_new'}, + showit => Echolot::Config::get()->{'show_new'}, + conf => { + nick => $nick, + type => $type, + capabilities => $caps, + last_update => $timestamp + } + }; + } else { + my $conf = $self->{'METADATA'}->{'remailers'}->{$address}->{'conf'}; + if ($conf->{'last_update'} >= $timestamp) { + warn ("Stored data is already newer for remailer $nick\n"); + return 1; + }; + $conf->{'last_update'} = $timestamp; + if ($conf->{'nick'} ne $nick) { + warn ($conf->{'nick'}." was renamed to $nick\n"); + $conf->{'nick'} = $nick; + }; + if ($conf->{'capabilities'} ne $caps) { + warn ("$nick has a new caps string '$caps' old: '".$conf->{'capabilities'}."'\n"); + $conf->{'capabilities'} = $caps; + }; + if ($conf->{'type'} ne $type) { + warn ("$nick has a new type string '$type'\n"); + $conf->{'type'} = $type; + }; + }; + $self->commit(); + + return 1; +}; + +sub get_secret($) { + my ($self) = @_; + + return $self->{'METADATA'}->{'secret'}; +}; + +=back + +=cut + +# vim: set ts=4 shiftwidth=4: diff --git a/Echolot/Tools.pm b/Echolot/Tools.pm new file mode 100644 index 0000000..06c6638 --- /dev/null +++ b/Echolot/Tools.pm @@ -0,0 +1,93 @@ +package Echolot::Tools; + +# (c) 2002 Peter Palfrader +# $Id: Tools.pm,v 1.1 2002/06/05 04:05:40 weasel Exp $ +# + +=pod + +=head1 Name + +Echolot::Tools - Tools for echolot + +=head1 DESCRIPTION + + +=cut + +use strict; +use warnings; +use Carp qw{cluck}; +use Digest::MD5 qw{}; +use Mail::Internet; + +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 + cluck("Cannot open ".Echolot::Config::get()->{'dev_random'}." for reading: $!"), + return 0; + read(FH, $random, $length) or + cluck("Cannot read from ".Echolot::Config::get()->{'dev_random'}.": $!"), + return 0; + close (FH) or + cluck("Cannot close ".Echolot::Config::get()->{'dev_random'}.": $!"), + return 0; + + $random = unpack('H*', $random) + if ($args{'armor'} == 1); + + return $random; +}; + + +sub send_message(%) { + my (%args) = @_; + + defined($args{'To'}) or + cluck ('No recipient address given'), + return 0; + $args{'Subject'} = '' unless (defined $args{'Subject'}); + $args{'Body'} = '' unless (defined $args{'Body'}); + if (defined $args{'Token'}) { + my $token = $args{'Token'}.'='.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; + $args{'From'} = + Echolot::Config::get()->{'my_localpart'}. + Echolot::Config::get()->{'recipient_delimiter'}. + $complete_token. + '@'. + Echolot::Config::get()->{'my_domain'}; + } else { + $args{'From'} = + Echolot::Config::get()->{'my_localpart'}. + '@'. + Echolot::Config::get()->{'my_domain'}; + }; + $args{'Subject'} = 'none' unless (defined $args{'Subject'}); + + my $message = "To: $args{'To'}\n"; + $message .= "From: $args{'From'}\n"; + $message .= "Subject: $args{'Subject'}\n"; + $message .= "\n".$args{'Body'}; + + my @lines = split (/\n/, $message); + my $mail = new Mail::Internet ( \@lines ); + + $mail->smtpsend( Host => Echolot::Config::get()->{'smarthost'} ); +}; + +1; + +# vim: set ts=4 shiftwidth=4: diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a45864d --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ + Echolot is (c) 2002 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA diff --git a/README b/README new file mode 100644 index 0000000..5f9809f --- /dev/null +++ b/README @@ -0,0 +1,28 @@ +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. + + +This is echolot2. Besides the name, author and purpose this has nothing to do +with echolot1. It's written from scratch. + +LICENSE +------- +Please see the file named "LICENSE". + + +REQUIREMENTS +------------ +FIXME + + +SETUP +----- +FIXME + diff --git a/pingd b/pingd new file mode 100755 index 0000000..21ce893 --- /dev/null +++ b/pingd @@ -0,0 +1,40 @@ +#!/usr/bin/perl -wT + +# (c) 2002 Peter Palfrader +# $Id: pingd,v 1.1 2002/06/05 04:05:40 weasel Exp $ +# + +use strict; +use XML::Parser; +use XML::Dumper; +use Getopt::Long; +use lib '.'; +use Echolot::Config; +use Echolot::Globals; +use Echolot::Storage::File; +#use Echolot::Scheduler; +use Echolot::Conf; +use Echolot::Mailin; + +$ENV{'PATH'} = '/bin:/usr/bin'; +delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; + +Echolot::Config::init(); +Echolot::Globals::init(); + +#my $storage = new Echolot::Storage::File ( datadir => 'data' ); +#my $conf = new Echolot::Conf( storage => $storage ); + +#$conf->send_requests(); + +#my @addr = $storage->get_query_addresses(); + +require Data::Dumper; +print Data::Dumper->Dump([Echolot::Globals::get()]); +#Echolot::Conf::send_requests(); +Echolot::Mailin::process(); + +#$storage->commit(); + + +# vim: set ts=4 shiftwidth=4: diff --git a/pingd.conf b/pingd.conf new file mode 100644 index 0000000..5760c57 --- /dev/null +++ b/pingd.conf @@ -0,0 +1,24 @@ + + + + + + + File + + + data + + + + + 5 + localhost + pinger + marvin.palfrader.org + mail/IN + mail/ERROR + 1 + 0 + + -- cgit v1.2.3