1package Net::RNDC;
2{
3  $Net::RNDC::VERSION = '0.003';
4}
5# ABSTRACT: Speak the BIND RNDC protocol
6
7use strict;
8use warnings;
9
10use Carp qw(croak);
11
12use Net::RNDC::Session;
13
14my $sock;
15
16BEGIN {
17	eval 'use IO::Socket::INET6;';
18
19	if ($@) {
20		eval 'use IO::Socket::INET;';
21
22		die $@ if $@;
23
24		$sock = 'IO::Socket::INET';
25	} else {
26		$sock = 'IO::Socket::INET6';
27	}
28}
29
30# Required for new()
31my @required_args = qw(
32);
33
34# Optional for new()/do()
35my @optional_args = qw(
36	key
37	host
38	port
39);
40
41sub new {
42	my ($class, %args) = @_;
43
44	my %obj = $class->_parse_args(%args);
45
46	return bless \%obj, $class;
47}
48
49sub _parse_args {
50	my ($class, %args) = @_;
51
52	for my $r (@required_args) {
53		unless ($args{$r}) {
54			croak("Required argument '$r' is missing");
55		}
56	}
57
58	$args{port} ||= 953;
59
60	return map {
61		$_ => $args{$_}
62	} grep { $args{$_} } (@required_args, @optional_args);
63}
64
65sub _check_do_args {
66	my ($self, %args) = @_;
67
68	for my $r (qw(key host)) {
69		unless ($args{$r}) {
70			croak("Required argument '$r' is missing");
71		}
72	}
73}
74
75sub do {
76	my ($self, $command, %override) = @_;
77
78	$self->{response} = $self->{error} = '';
79
80	my $host = $self->{host};
81	my $port = $self->{port};
82	my $key  = $self->{key};
83
84	if (%override) {
85		my %args = $self->_parse_args(
86			host => $host,
87			port => $port,
88			key => $key,
89			%override,
90		);
91
92		$host = $args{host};
93		$port = $args{port};
94		$key  = $args{key};
95	}
96
97	$self->_check_do_args(
98		host => $host,
99		port => $port,
100		key  => $key,
101	);
102
103	my $c = $sock->new(
104		PeerAddr => "$host:$port",
105	);
106
107	unless ($c) {
108		$self->{error} = "Failed to create a socket: $@ ($!)";
109
110		return 0;
111	}
112
113	# Net::RNDC::Session does all of the work
114	my $sess = Net::RNDC::Session->new(
115		key         => $key,
116		command     => $command,
117		is_client   => 1,
118
119		want_write => sub {
120			my $s = shift;
121
122			$c->send(shift);
123
124			$s->next;
125		},
126
127		want_read => sub {
128			my $s = shift;
129
130			my $buff;
131
132			$c->recv($buff, 4096);
133
134			$s->next($buff);
135		},
136
137		want_finish => sub {
138			my $s = shift;
139			my $res = shift;
140
141			$self->{response} = $res;
142		},
143
144		want_error => sub {
145			my $s = shift;
146			my $err = shift;
147
148			$self->{error} = $err;
149		}
150	);
151
152	# Work!
153	$sess->start;
154
155	$c->close;
156
157	if ($self->response) {
158		return 1;
159	} else {
160		return 0;
161	}
162}
163
164sub response {
165	my ($self) = @_;
166
167	return $self->{response};
168}
169
170sub error {
171	my ($self) = @_;
172
173	return $self->{error};
174}
175
1761;
177__END__;
178
179=head1 NAME
180
181Net::RNDC - Speak the BIND Remote Name Daemon Control (RNDC) V1 protocol
182
183=head1 VERSION
184
185version 0.003
186
187=head1 SYNOPSIS
188
189Simple synchronous command/response:
190
191  use Net::RNDC;
192
193  my $rndc = Net::RNDC->new(
194    host => '127.0.0.1',
195    port => 953,         # Defaults to 953
196    key  => 'abcd',
197  );
198
199  if (!$rndc->do('status')) {
200    die "RNDC failed: " . $rndc->error;
201  }
202
203  print $rndc->response;
204
205All arguments to new() are allowed in do:
206
207  my $rndc = Net::RNDC->new();
208
209  my $key = 'abcd';
210
211  for my $s (qw(127.0.0.1 127.0.0.2)) {
212    if (!$rndc->do('status', key => $key, host => $s)) {
213      my $err = $rndc->error;
214    } else {
215      my $resp = $rndc->response;
216    }
217  }
218
219=head1 DESCRIPTION
220
221This package provides a synchronous, easy to use interface to the RNDC V1
222protocol. For more mid-level control, see L<Net::RNDC::Session>, and for
223absolute control, L<Net::RNDC::Packet>.
224
225=head2 Constructor
226
227=head3 new
228
229  Net::RNDC->new(%args);
230
231Optional Arguments:
232
233=over 4
234
235=item *
236
237B<key> - The Base64 encoded HMAC-MD5 private key to use.
238
239=item *
240
241B<host> - The hostname/IP of the remote server to connect to. If
242L<IO::Socket::INET6> is installed, IPv6 support will be enabled.
243
244=item *
245
246B<port> - The port to connect to. Defaults to I<953>.
247
248=back
249
250=head2 Methods
251
252=head3 do
253
254  $rndc->do($command);
255
256  $rndc->do($commands, %args);
257
258Connects to the remote nameserver configured in L</new> or passed in to
259B<%args> and sends the specified command.
260
261Returns 1 on success, 0 on failure.
262
263Arguments:
264
265=over 4
266
267=item *
268
269B<$command> - The RNDC command to run. For example: C<status>.
270
271=back
272
273Optional Arguments - See L</new> above.
274
275=head3 error
276
277  $rndc->error;
278
279Returns the last string error from a call to L</do>, if any. Only set if
280L</do> returns 0.
281
282=head3 response
283
284  $rndc->response;
285
286Returns the last string response from a call to L</do>, if any. Only set if
287L</do> returns 1.
288
289=head1 SEE ALSO
290
291L<Net::RNDC::Session> - Manage the 4-packet RNDC session
292
293L<Net::RNDC::Packet> - Low level RNDC packet manipulation.
294
295=head1 AUTHOR
296
297Matthew Horsfall (alh) <WolfSage@gmail.com>
298
299=head1 LICENSE
300
301You may distribute this code under the same terms as Perl itself.
302
303=cut
304