Репозиторий Sisyphus
Последнее обновление: 1 октября 2023 | Пакетов: 18631 | Посещений: 37793206
en ru br
Репозитории ALT

Группа :: Разработка/Perl
Пакет: perl-EV-ADNS

 Главная   Изменения   Спек   Патчи   Исходники   Загрузить   Gear   Bugs and FR  Repocop 

EV-ADNS-2.2/000075500000000000000000000000001230154146000124045ustar00rootroot00000000000000EV-ADNS-2.2/ADNS.pm000064400000000000000000000074771230154146000135060ustar00rootroot00000000000000=head1 NAME

EV::ADNS - lightweight asynchronous dns queries using EV and libadns

=head1 SYNOPSIS

use EV;
use EV::ADNS;

EV::ADNS::submit "example.com", EV::ADNS::r_addr, 0, sub {
my ($status, $expires, @a) = @_;
warn $a[0]; # "127.13.166.3" etc.
};

EV::loop;

=head1 DESCRIPTION

This is a simple interface to libadns (asynchronous dns) that
integrates well and automatically into the EV event loop. The
documentation for libadns is vital to understand this module, see
L<http://www.chiark.greenend.org.uk/~ian/adns/>.

You can use it only with EV (directly or indirectly, e.g. via
L<Glib::EV>). Apart from loading and using the C<submit> function you need
not do anything (except run an EV event loop).

=head1 OVERVIEW

All the constants/enums from F<adns.h> are available in the EV::ADNS
namespace, without the C<adns_> prefix, e.g. C<adns_r_a> becomes
C<EV::ADNS::r_a>, C<adns__qtf_deref> becomes C<EV::ADNS::_qtf_deref> and
so on.

=head1 FUNCTIONS

=over 4

=item $query = EV::ADNS::submit "domain", $rrtype, $flags, $cb

Submits a new request to be handled. See the C<adns_submit> C function
description for more details. The function optionally returns a query
object which can be used to cancel an in-progress request. You do not need
to store the query object, even if you ignore it the query will proceed.

The callback will be invoked with a result status, the time the resource
record validity expires and zero or more resource records, one scalar per
result record. Example:

sub adns_cb {
my ($status, $expires, @rr) = @_;
if ($status == EV::ADNS::s_ok) {
use JSON::XS;
warn encode_json \@rr;
}
}

The format of result records varies considerably, here is some cursory
documentation of how each record will look like, depending on the query
type:

=over 4

=item EV::ADNS::r_a, EV::ADNS::r_addr

An IPv4 address in dotted quad (string) form.

=item EV::ADNS::r_ns_raw, EV::ADNS::r_cname, EV::ADNS::r_ptr, EV::ADNS::r_ptr_raw

The resource record as a simple string.

=item EV::ADNS::r_txt

An arrayref of strings.

=item EV::ADNS::r_ns

A "host address", a hostname with any number of addresses (hint records).

Currently only the hostname will be stored, so this is alway an arrayref
with a single element of the hostname. Future versions might add
additional address entries.

=item EV::ADNS::r_hinfo

An arrayref consisting of the two strings.

=item EV::ADNS::r_rp, EV::ADNS::r_rp_raw

An arrayref with two strings.

=item EV::ADNS::r_mx

An arrayref consisting of the priority and a "host address" (see
C<EV::ADNS::r_ns>). Example:

[10, "mail10.example.com"]

=item EV::ADNS::r_mx_raw

An arrayref consisting of the priority and the hostname, e.g. C<[10,
"mail.example.com"]>.

=item EV::ADNS::r_soa, EV::ADNS::r_soa_raw

An arrayref consisting of the primary nameserver, admin name, serial,
refresh, retry expire and minimum times, e.g.:

["ns.example.net", "hostmaster@example.net", 2000001102, 86400, 21600, 2592000, 172800]

The "raw" form doesn't mangle the e-mail address.

=item EV::ADNS::r_srv_raw

An arrayref consisting of the priority, weight, port and hostname, e.g.:

[10, 10, 5060, "sip1.example.net"]

=item EV::ADNS::r_srv

The same as C<EV::ADNS::r_srv_raw>, but the hostname is replaced by a "host
address" (see C<EV::ADNS::r_ns>).

=item EV::ADNS::r_unknown

A single octet string with the raw contents.

=item anything else

Currently C<undef>.

=back

=item $query->cancel

Cancels a request that is in progress.

=back

=cut

package EV::ADNS;

use Carp ();
use EV ();

BEGIN {
$VERSION = '2.2';

require XSLoader;
XSLoader::load (EV::ADNS, $VERSION);
}

=head1 SEE ALSO

L<EV>, L<Net::ADNS> another interface to adns, maybe better, but without
real support to integrate it into other event loops.

=head1 AUTHOR

Marc Lehmann <schmorp@schmorp.de>
http://home.schmorp.de/

=cut

1

EV-ADNS-2.2/ADNS.xs000064400000000000000000000272711230154146000135160ustar00rootroot00000000000000#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <poll.h>
#include <adns.h>

#include "EVAPI.h"

static struct pollfd *fds;
static int nfd, mfd;
static ev_io *iow;
static ev_timer tw;
static ev_idle iw;
static ev_prepare pw;
static struct timeval tv_now;
static int outstanding;

static void
outstanding_inc (adns_state ads)
{
if (!outstanding++)
ev_prepare_start (EV_DEFAULT, &pw);
}

static void
outstanding_dec (adns_state ads)
{
--outstanding;
}

struct ctx
{
SV *self;
adns_state ads;
adns_query query;
SV *cb;
};

static SV *
ha2sv (adns_rr_hostaddr *rr)
{
AV *av = newAV ();
av_push (av, newSVpv (rr->host, 0));
// TODO: add addresses

return newRV_noinc ((SV *)av);
}

static void
process (adns_state ads)
{
dSP;

ENTER;
SAVETMPS;

for (;;)
{
int i;
adns_query q = 0;
adns_answer *a;
void *ctx;
SV *cb;
struct ctx *c;
int r = adns_check (ads, &q, &a, &ctx);

if (r)
break;

c = (struct ctx *)ctx;
cb = c->cb;
c->cb = 0; outstanding_dec (ads);
SvREFCNT_dec (c->self);

assert (cb);

PUSHMARK (SP);

EXTEND (SP, a->nrrs + 2);
PUSHs (sv_2mortal (newSViv (a->status)));
PUSHs (sv_2mortal (newSViv (a->expires)));

for (i = 0; i < a->nrrs; ++i)
{
SV *sv;

switch (a->type & adns_r_unknown ? adns_r_unknown : a->type)
{
case adns_r_ns_raw:
case adns_r_cname:
case adns_r_ptr:
case adns_r_ptr_raw:
sv = newSVpv (a->rrs.str [i], 0);
break;

case adns_r_txt:
{
AV *av = newAV ();
adns_rr_intstr *rr = a->rrs.manyistr [i];

while (rr->str)
{
av_push (av, newSVpvn (rr->str, rr->i));
++rr;
}

sv = newRV_noinc ((SV *)av);
}
break;

case adns_r_addr:
sv = newSVpv (inet_ntoa (a->rrs.addr [i].addr.inet.sin_addr), 0);
break;

case adns_r_a:
sv = newSVpv (inet_ntoa (a->rrs.inaddr [i]), 0);
break;

case adns_r_ns:
sv = ha2sv (a->rrs.hostaddr + i);
break;

case adns_r_hinfo:
{
/* untested */
AV *av = newAV ();
adns_rr_intstrpair *rr = a->rrs.intstrpair + i;

av_push (av, newSVpvn (rr->array [0].str, rr->array [0].i));
av_push (av, newSVpvn (rr->array [1].str, rr->array [1].i));

sv = newRV_noinc ((SV *)av);
}
break;

case adns_r_rp:
case adns_r_rp_raw:
{
/* untested */
AV *av = newAV ();
adns_rr_strpair *rr = a->rrs.strpair + i;

av_push (av, newSVpv (rr->array [0], 0));
av_push (av, newSVpv (rr->array [1], 0));

sv = newRV_noinc ((SV *)av);
}
break;

case adns_r_mx:
{
AV *av = newAV ();
adns_rr_inthostaddr *rr = a->rrs.inthostaddr + i;

av_push (av, newSViv (rr->i));
av_push (av, ha2sv (&rr->ha));

sv = newRV_noinc ((SV *)av);
}
break;

case adns_r_mx_raw:
{
AV *av = newAV ();
adns_rr_intstr *rr = a->rrs.intstr + i;

av_push (av, newSViv (rr->i));
av_push (av, newSVpv (rr->str, 0));

sv = newRV_noinc ((SV *)av);
}
break;

case adns_r_soa:
case adns_r_soa_raw:
{
AV *av = newAV ();
adns_rr_soa *rr = a->rrs.soa + i;

av_push (av, newSVpv (rr->mname, 0));
av_push (av, newSVpv (rr->rname, 0));
av_push (av, newSVuv (rr->serial));
av_push (av, newSVuv (rr->refresh));
av_push (av, newSVuv (rr->retry));
av_push (av, newSVuv (rr->expire));
av_push (av, newSVuv (rr->minimum));

sv = newRV_noinc ((SV *)av);
}
break;

case adns_r_srv_raw:
{
AV *av = newAV ();
adns_rr_srvraw *rr = a->rrs.srvraw + i;

av_push (av, newSViv (rr->priority));
av_push (av, newSViv (rr->weight));
av_push (av, newSViv (rr->port));
av_push (av, newSVpv (rr->host, 0));

sv = newRV_noinc ((SV *)av);
}
break;

case adns_r_srv:
{
AV *av = newAV ();
adns_rr_srvha *rr = a->rrs.srvha + i;

av_push (av, newSViv (rr->priority));
av_push (av, newSViv (rr->weight));
av_push (av, newSViv (rr->port));
av_push (av, ha2sv (&rr->ha));

sv = newRV_noinc ((SV *)av);
}
break;

case adns_r_unknown:
sv = newSVpvn (a->rrs.byteblock [i].data, a->rrs.byteblock [i].len);
break;

default:
sv = &PL_sv_undef; /* not supported */
break;
}

PUSHs (sv_2mortal (sv));
}

free (a);

PUTBACK;
call_sv (cb, G_VOID | G_DISCARD | G_EVAL);
SPAGAIN;

if (SvTRUE (ERRSV))
warn ("%s", SvPV_nolen (ERRSV));

SvREFCNT_dec (cb);
}

FREETMPS;
LEAVE;
}

static void
update_now (EV_P)
{
ev_tstamp t = ev_now (EV_A);

tv_now.tv_sec = (long)t;
tv_now.tv_usec = (long)((t - (ev_tstamp)tv_now.tv_sec) * 1e6);
}

static void
idle_cb (EV_P_ ev_idle *w, int revents)
{
ev_idle_stop (EV_A, w);
}

static void
timer_cb (EV_P_ ev_timer *w, int revents)
{
adns_state ads = (adns_state)w->data;
update_now (EV_A);

adns_processtimeouts (ads, &tv_now);
}

static void
io_cb (EV_P_ ev_io *w, int revents)
{
adns_state ads = (adns_state)w->data;
update_now (EV_A);

if (revents & EV_READ ) adns_processreadable (ads, w->fd, &tv_now);
if (revents & EV_WRITE) adns_processwriteable (ads, w->fd, &tv_now);
}

// create io watchers for each fd and a timer before blocking
static void
prepare_cb (EV_P_ ev_prepare *w, int revents)
{
int i;
int timeout = 3600000;
adns_state ads = (adns_state)w->data;

if (ev_is_active (&tw))
ev_timer_stop (EV_A, &tw);

if (ev_is_active (&iw))
ev_idle_stop (EV_A, &iw);

for (i = 0; i < nfd; ++i)
ev_io_stop (EV_A, iow + i);

process (ads);

if (!outstanding)
{
ev_prepare_stop (EV_A, w);
return;
}

update_now (EV_A);

nfd = mfd;

while (adns_beforepoll (ads, fds, &nfd, &timeout, &tv_now))
{
mfd = nfd;

free (iow); iow = malloc (mfd * sizeof (ev_io));
free (fds); fds = malloc (mfd * sizeof (struct pollfd));
}

ev_timer_set (&tw, timeout * 1e-3, 0.);
ev_timer_start (EV_A, &tw);

// create one ev_io per pollfd
for (i = 0; i < nfd; ++i)
{
ev_io *w = iow + i;

ev_io_init (w, io_cb, fds [i].fd,
((fds [i].events & POLLIN ? EV_READ : 0)
| (fds [i].events & POLLOUT ? EV_WRITE : 0)));

w->data = (void *)ads;
ev_io_start (EV_A, w);
}
}

static HV *stash;
static adns_state ads;

MODULE = EV::ADNS PACKAGE = EV::ADNS

PROTOTYPES: ENABLE

BOOT:
{
stash = gv_stashpv ("EV::ADNS", 1);

static const struct {
const char *name;
IV iv;
} *civ, const_iv[] = {
# define const_iv(name) { # name, (IV) adns_ ## name },
const_iv (if_none)
const_iv (if_noenv)
const_iv (if_noerrprint)
const_iv (if_noserverwarn)
const_iv (if_debug)
const_iv (if_logpid)
const_iv (if_noautosys)
const_iv (if_eintr)
const_iv (if_nosigpipe)
const_iv (if_checkc_entex)
const_iv (if_checkc_freq)

const_iv (qf_none)
const_iv (qf_search)
const_iv (qf_usevc)
const_iv (qf_owner)
const_iv (qf_quoteok_query)
const_iv (qf_quoteok_cname)
const_iv (qf_quoteok_anshost)
const_iv (qf_quotefail_cname)
const_iv (qf_cname_loose)
const_iv (qf_cname_forbid)

const_iv (rrt_typemask)
const_iv (_qtf_deref)
const_iv (_qtf_mail822)
const_iv (r_unknown)
const_iv (r_none)
const_iv (r_a)
const_iv (r_ns_raw)
const_iv (r_ns)
const_iv (r_cname)
const_iv (r_soa_raw)
const_iv (r_soa)
const_iv (r_ptr_raw)
const_iv (r_ptr)
const_iv (r_hinfo)
const_iv (r_mx_raw)
const_iv (r_mx)
const_iv (r_txt)
const_iv (r_rp_raw)
const_iv (r_rp)
const_iv (r_srv_raw)
const_iv (r_srv)
const_iv (r_addr)

const_iv (s_ok)
const_iv (s_nomemory)
const_iv (s_unknownrrtype)
const_iv (s_systemfail)
const_iv (s_max_localfail)
const_iv (s_timeout)
const_iv (s_allservfail)
const_iv (s_norecurse)
const_iv (s_invalidresponse)
const_iv (s_unknownformat)
const_iv (s_max_remotefail)
const_iv (s_rcodeservfail)
const_iv (s_rcodeformaterror)
const_iv (s_rcodenotimplemented)
const_iv (s_rcoderefused)
const_iv (s_rcodeunknown)
const_iv (s_max_tempfail)
const_iv (s_inconsistent)
const_iv (s_prohibitedcname)
const_iv (s_answerdomaininvalid)
const_iv (s_answerdomaintoolong)
const_iv (s_invaliddata)
const_iv (s_max_misconfig)
const_iv (s_querydomainwrong)
const_iv (s_querydomaininvalid)
const_iv (s_querydomaintoolong)
const_iv (s_max_misquery)
const_iv (s_nxdomain)
const_iv (s_nodata)
const_iv (s_max_permfail)
};

for (civ = const_iv + sizeof (const_iv) / sizeof (const_iv [0]); civ-- > const_iv; )
newCONSTSUB (stash, (char *)civ->name, newSViv (civ->iv));

I_EV_API ("EV::ADNS");

adns_init (&ads, adns_if_noenv | adns_if_noerrprint | adns_if_noserverwarn | adns_if_noautosys, 0);

ev_prepare_init (&pw, prepare_cb);
pw.data = (void *)ads;

ev_init (&iw, idle_cb); ev_set_priority (&iw, EV_MINPRI);
iw.data = (void *)ads;
ev_init (&tw, timer_cb);
tw.data = (void *)ads;
}

void submit (char *owner, int type, int flags, SV *cb)
PPCODE:
{
SV *csv = NEWSV (0, sizeof (struct ctx));
struct ctx *c = (struct ctx *)SvPVX (csv);
int r = adns_submit (ads, owner, type, flags, (void *)c, &c->query);

outstanding_inc (ads);

if (r)
{
SvREFCNT_dec (csv);
errno = r;
XSRETURN_EMPTY;
}
else
{
SvPOK_only (csv);
SvCUR_set (csv, sizeof (struct ctx));

c->self = csv;
c->cb = newSVsv (cb);
c->ads = ads;

if (!ev_is_active (&iw))
ev_idle_start (EV_DEFAULT, &iw);

if (GIMME_V != G_VOID)
{
csv = sv_2mortal (newRV_inc (csv));
sv_bless (csv, stash);
XPUSHs (csv);
}
}
}

void DESTROY (SV *req)
ALIAS:
cancel = 1
CODE:
{
struct ctx *c;

if (!(SvROK (req) && SvOBJECT (SvRV (req))
&& (SvSTASH (SvRV (req)) == stash)))
croak ("object is not of type EV::ADNS");

c = (struct ctx *)SvPVX (SvRV (req));

if (c->cb)
{
SvREFCNT_dec (c->cb);
c->cb = 0; outstanding_dec (c->ads);
adns_cancel (c->query);
SvREFCNT_dec (c->self);
}
}

EV-ADNS-2.2/COPYING000064400000000000000000000000761230154146000134420ustar00rootroot00000000000000This module is licensed under the same terms as perl itself.

EV-ADNS-2.2/Changes000064400000000000000000000016441230154146000137040ustar00rootroot00000000000000Revision history for EV::ADNS

2.2 Fri May 7 23:40:40 CEST 2010
- avoid accumulating temporaries, which can "leak" memory
in certain cases (testcase by Nikita Savin).

2.1 2008-05-17
- support r_addr result type.

2.0 Sat Dec 22 17:49:53 CET 2007
- upgrade to EV version 2.0 API.
- add message about adns-1.4 being the minimum supported version.

1.0 Thu Dec 13 00:44:24 CET 2007
- only run the prepare watcher if there actually is work to do
(this also made the code simpler and faster).
- possible future multiplicity support.

0.3 Mon Dec 3 20:02:40 CET 2007
- properly call adns_processtimeouts in the timer callback.

0.2 Mon Dec 3 12:53:55 CET 2007
- use an idle watcher to ensure that our prepare callback
is called even when we submit requests late in the event loop.
- doc fixes.

0.1 Sat Dec 1 23:35:27 CET 2007
- original version, a Glib::EV clone.
EV-ADNS-2.2/MANIFEST000064400000000000000000000002271230154146000135360ustar00rootroot00000000000000README
MANIFEST
COPYING
Changes
Makefile.PL
ADNS.pm
ADNS.xs
t/00_load.t
META.yml Module meta-data (added by MakeMaker)
EV-ADNS-2.2/META.yml000064400000000000000000000007321230154146000136570ustar00rootroot00000000000000--- #YAML:1.0
name: EV-ADNS
version: 2.2
abstract: ~
author: []
license: unknown
distribution_type: module
configure_requires:
ExtUtils::MakeMaker: 0
build_requires:
ExtUtils::MakeMaker: 0
requires:
EV: 2
no_index:
directory:
- t
- inc
generated_by: ExtUtils::MakeMaker version 6.55_02
meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html
version: 1.4
EV-ADNS-2.2/Makefile.PL000064400000000000000000000017371230154146000143660ustar00rootroot00000000000000use ExtUtils::MakeMaker;
use EV::MakeMaker;

print <<EOF;

***
*** This is an interface to the asynchronous dns resolver library, libadns
*** You need to have it installed before using this module.
***
*** This module has only been tested with adns-1.4, earlier versions might
*** work, but are not supported (upgrading is highly recommended due to
*** the many bugs in earlier versions).
***
*** For Debian GNU/Linux and Ubuntu, this would get you there:
*** apt-get install libadns1-dev
***

EOF

my $mm = MM->new({EV::MakeMaker::ev_args (
dist => {
PREOP => 'pod2text ADNS.pm | tee README >$(DISTVNAME)/README; chmod -R u=rwX,go=rX . ;',
COMPRESS => 'gzip -9v',
SUFFIX => '.gz',
},
NAME => "EV::ADNS",
VERSION_FROM => "ADNS.pm",
LIBS => ["-ladns"],
PREREQ_FATAL => 1,
PREREQ_PM => {
EV => 2.0,
},
EXTRA_META => q{
configure_requires:
EV: 2.0
},

)});

$mm->flush;

EV-ADNS-2.2/README000064400000000000000000000101601230154146000132620ustar00rootroot00000000000000NAME
EV::ADNS - lightweight asynchronous dns queries using EV and libadns

SYNOPSIS
use EV;
use EV::ADNS;

EV::ADNS::submit "example.com", EV::ADNS::r_addr, 0, sub {
my ($status, $expires, @a) = @_;
warn $a[0]; # "127.13.166.3" etc.
};

EV::loop;

DESCRIPTION
This is a simple interface to libadns (asynchronous dns) that integrates
well and automatically into the EV event loop. The documentation for
libadns is vital to understand this module, see
<http://www.chiark.greenend.org.uk/~ian/adns/>.

You can use it only with EV (directly or indirectly, e.g. via Glib::EV).
Apart from loading and using the "submit" function you need not do
anything (except run an EV event loop).

OVERVIEW
All the constants/enums from adns.h are available in the EV::ADNS
namespace, without the "adns_" prefix, e.g. "adns_r_a" becomes
"EV::ADNS::r_a", "adns__qtf_deref" becomes "EV::ADNS::_qtf_deref" and so
on.

FUNCTIONS
$query = EV::ADNS::submit "domain", $rrtype, $flags, $cb
Submits a new request to be handled. See the "adns_submit" C
function description for more details. The function optionally
returns a query object which can be used to cancel an in-progress
request. You do not need to store the query object, even if you
ignore it the query will proceed.

The callback will be invoked with a result status, the time the
resource record validity expires and zero or more resource records,
one scalar per result record. Example:

sub adns_cb {
my ($status, $expires, @rr) = @_;
if ($status == EV::ADNS::s_ok) {
use JSON::XS;
warn encode_json \@rr;
}
}

The format of result records varies considerably, here is some
cursory documentation of how each record will look like, depending
on the query type:

EV::ADNS::r_a, EV::ADNS::r_addr
An IPv4 address in dotted quad (string) form.

EV::ADNS::r_ns_raw, EV::ADNS::r_cname, EV::ADNS::r_ptr,
EV::ADNS::r_ptr_raw
The resource record as a simple string.

EV::ADNS::r_txt
An arrayref of strings.

EV::ADNS::r_ns
A "host address", a hostname with any number of addresses (hint
records).

Currently only the hostname will be stored, so this is alway an
arrayref with a single element of the hostname. Future versions
might add additional address entries.

EV::ADNS::r_hinfo
An arrayref consisting of the two strings.

EV::ADNS::r_rp, EV::ADNS::r_rp_raw
An arrayref with two strings.

EV::ADNS::r_mx
An arrayref consisting of the priority and a "host address" (see
"EV::ADNS::r_ns"). Example:

[10, "mail10.example.com"]

EV::ADNS::r_mx_raw
An arrayref consisting of the priority and the hostname, e.g.
"[10, "mail.example.com"]".

EV::ADNS::r_soa, EV::ADNS::r_soa_raw
An arrayref consisting of the primary nameserver, admin name,
serial, refresh, retry expire and minimum times, e.g.:

["ns.example.net", "hostmaster@example.net", 2000001102, 86400, 21600, 2592000, 172800]

The "raw" form doesn't mangle the e-mail address.

EV::ADNS::r_srv_raw
An arrayref consisting of the priority, weight, port and
hostname, e.g.:

[10, 10, 5060, "sip1.example.net"]

EV::ADNS::r_srv
The same as "EV::ADNS::r_srv_raw", but the hostname is replaced
by a "host address" (see "EV::ADNS::r_ns").

EV::ADNS::r_unknown
A single octet string with the raw contents.

anything else
Currently "undef".

$query->cancel
Cancels a request that is in progress.

SEE ALSO
EV, Net::ADNS another interface to adns, maybe better, but without real
support to integrate it into other event loops.

AUTHOR
Marc Lehmann <schmorp@schmorp.de>
http://home.schmorp.de/

EV-ADNS-2.2/t/000075500000000000000000000000001230154146000126475ustar00rootroot00000000000000EV-ADNS-2.2/t/00_load.t000064400000000000000000000001661230154146000142550ustar00rootroot00000000000000BEGIN { $| = 1; print "1..1\n"; }
END {print "not ok 1\n" unless $loaded;}
use EV::ADNS;
$loaded = 1;
print "ok 1\n";
 
дизайн и разработка: Vladimir Lettiev aka crux © 2004-2005, Andrew Avramenko aka liks © 2007-2008
текущий майнтейнер: Michael Shigorin