CJDNS.pm 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. package CJDNS;
  2. use Bencode qw(bencode bdecode);
  3. use Carp qw/croak/;
  4. use Digest::SHA2;
  5. use IO::Socket;
  6. # buffer size for reading from teh sawkets.
  7. use constant BUFFER_SIZE => 8192;
  8. our @ISA = qw();
  9. our $VERSION = '0.01';
  10. our $sha2 = new Digest::SHA2 256;
  11. # turn on autoflush for this class.
  12. our $| = 1;
  13. sub new {
  14. my ($class, $addr, $port, $password) = @_;
  15. my $self = bless({
  16. connection => "$addr:$port",
  17. password => $password,
  18. }, $class);
  19. $self->{s} = IO::Socket::INET->new(
  20. PeerAddr => $addr,
  21. PeerPort => $port,
  22. Proto => 'udp',
  23. Type => SOCK_DGRAM
  24. );
  25. unless ($self->_ping) {
  26. die "Can't ping cjdns admin interface at udp://$addr:$port\n";
  27. }
  28. $self->_make_methods;
  29. return $self;
  30. }
  31. sub _make_methods {
  32. my ($self) = @_;
  33. my $s = $self->s;
  34. my $availableFunctions;
  35. my $page = 0;
  36. while (1) {
  37. my $to_decode;
  38. print $s "d1:q24:Admin_availableFunctions4:argsd4:pagei$page" . "eee";
  39. # grab the data and rock it out!
  40. recv($s, $to_decode, BUFFER_SIZE, undef);
  41. my $decoded = bdecode($to_decode);
  42. # copy the hash!
  43. foreach my $key (keys %{$decoded->{availableFunctions}}) {
  44. $availableFunctions->{$key} = $decoded->{availableFunctions}->{$key};
  45. }
  46. last unless exists $decoded->{more};
  47. # get the next page.
  48. $page++;
  49. }
  50. # first let's start by loading them as named into the cjdns namespace.
  51. foreach my $method_name (keys %$availableFunctions) {
  52. my $prototype = $availableFunctions->{$method_name};
  53. $self->{capabilities}->{$method_name} = $prototype;
  54. # This is the code that actually calls the function!
  55. my $method = sub {
  56. my ($self, %args) = @_;
  57. my $s = $self->s;
  58. my $to_decode;
  59. print $s "d1:q6:cookiee";
  60. recv($s, $to_decode, BUFFER_SIZE, undef);
  61. my $dec = bdecode($to_decode);
  62. my $cookie = $dec->{cookie};
  63. my $req = {
  64. q => 'auth',
  65. aq => $method_name,
  66. hash => $self->_sha2_hexdigest($self->{password} . $cookie),
  67. cookie => " $cookie",
  68. args => \%args,
  69. };
  70. # replace $req->{hash} with a hash of the bencoded request.
  71. my $req_benc = bencode($req);
  72. $req->{hash} = $self->_sha2_hexdigest($req_benc);
  73. # then re-encode thusly:
  74. $req_benc = bencode($req);
  75. print $s $req_benc;
  76. my $to_decode;
  77. recv($s, $to_decode, BUFFER_SIZE, undef);
  78. my $dec = bdecode($to_decode);
  79. if (ref($dec)) {
  80. if ($dec->{error}) {
  81. croak "[error] CJDNS method '$method_name': $dec->{error}";
  82. }
  83. }
  84. return $dec;
  85. };
  86. # and now it's a method!
  87. my $full_name = "CJDNS::$method_name";
  88. *{$full_name} = $method;
  89. }
  90. }
  91. sub capabilities {
  92. my ($self) = @_;
  93. my $return = "Cjdns Administration Protocol Capabilities\n";
  94. $return .= "------------------------------------------\n";
  95. foreach my $func (keys %{$self->{capabilities}}) {
  96. $return .= " $func\n";
  97. foreach my $attr (keys %{$self->{capabilities}->{$func}}) {
  98. $return .= " + $attr: $self->{capabilities}->{$func}->{$attr}->{type} ";
  99. if ($self->{capabilities}->{$func}->{$attr}->{required}) {
  100. $return .= "[required]";
  101. }
  102. $return .= "\n";
  103. }
  104. $return .= "\n";
  105. }
  106. return $return;
  107. }
  108. sub _sha2_hexdigest {
  109. my ($self, $string) = @_;
  110. $sha2->reset();
  111. $sha2->add($string);
  112. return $sha2->hexdigest;
  113. }
  114. sub _ping {
  115. my ($self) = @_;
  116. my $s = $self->s;
  117. my $data;
  118. print $s "d1:q4:pinge";
  119. recv($s, $data, BUFFER_SIZE, undef);
  120. if ($data eq "d1:q4:ponge") {
  121. return 1;
  122. } else {
  123. return undef;
  124. }
  125. }
  126. sub s {
  127. my ($self) = @_;
  128. return $self->{s};
  129. }
  130. 1;
  131. __END__
  132. =head1 NAME
  133. Cjdns - Perl interface to the Cjdns Administration Interface
  134. =head1 SYNOPSIS
  135. use CJDNS;
  136. my $cjdns = CJDNS->new('localhost', '12345', 'abc123');
  137. printf("Cjdns' routing table is using %d bytes of memory!\n", $cjdns->memory->{bytes});
  138. =head1 DESCRIPTION
  139. Perl interface to the cjdns Administration system
  140. =head1 SEE ALSO
  141. https://github.com/cjdelisle/cjdns
  142. =head1 AUTHOR
  143. Michael Gregorowicz, E<lt>mikei@mg2.orgE<gt>
  144. =head1 COPYRIGHT AND LICENSE
  145. Copyright (C) 2012 by Michael Gregorowicz
  146. This library is free software; you can redistribute it and/or modify
  147. it under the same terms as Perl itself, either Perl version 5.14.2 or,
  148. at your option, any later version of Perl 5 you may have available.
  149. =cut