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/doc/methodology | 60 ++++ trunk/doc/pingd.conf.pod | 910 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 970 insertions(+) create mode 100644 trunk/doc/methodology create mode 100644 trunk/doc/pingd.conf.pod (limited to 'trunk/doc') 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 -- cgit v1.2.3