]> git.refcnt.org Git - distdns.git/blob - client.pl
Record and list last updated time
[distdns.git] / client.pl
1 #!/usr/bin/perl
2 #
3 # Copyright (c) 2013 Michel Ketterle, Steven Schubiger
4 #
5 # This file is part of distdns.
6 #
7 # distdns is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # distdns is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with distdns. If not, see <http://www.gnu.org/licenses/>.
19
20 use strict;
21 use warnings;
22 use constant false => 0;
23
24 use Config::Tiny;
25 use Digest::MD5 qw(md5_hex);
26 use Fcntl ':flock';
27 use File::Spec::Functions qw(catfile rel2abs);
28 use FindBin qw($Bin);
29 use Getopt::Long qw(:config no_auto_abbrev no_ignore_case);
30 use JSON qw(decode_json);
31 use LWP::UserAgent;
32 use POSIX qw(strftime);
33 use Sys::Hostname qw(hostname);
34 use Tie::File;
35
36 my $VERSION = '0.06';
37
38 my $conf_file = catfile($Bin, 'client.conf');
39
40 sub _die { die "$0: [client] $_[0]" }
41
42 sub usage
43 {
44 warn "$_[0]\n" if defined $_[0];
45 print <<"USAGE";
46 Usage: $0 [options]
47 -d, --debug server debugging
48 -h, --help this help screen
49 -i, --init initialize session data
50 -l, --list list remote entries
51 USAGE
52 exit;
53 }
54
55 my %opts;
56 GetOptions(\%opts, qw(d|debug h|help i|init l|list)) or usage();
57 usage() if $opts{h};
58
59 usage('Cannot combine --init and --list') if $opts{i} && $opts{l};
60
61 my $config = Config::Tiny->new;
62 $config = Config::Tiny->read($conf_file);
63
64 my $get_config_opts = sub
65 {
66 my ($section, $options) = @_;
67
68 _die "Section '$section' missing in $conf_file\n" unless exists $config->{$section};
69
70 my %options;
71 @options{@$options} = @{$config->{$section}}{@$options};
72
73 foreach my $option (@$options) {
74 _die "Option '$option' not set in $conf_file\n" unless defined $options{$option} && length $options{$option};
75 }
76
77 return @options{@$options};
78 };
79
80 my ($hosts_file, $session_file) = map rel2abs($_, $Bin), $get_config_opts->('path', [ qw(hosts_file session_file) ]);
81
82 my ($server_url) = $get_config_opts->('url', [ qw(server_url) ]);
83 my ($netz, $name) = $get_config_opts->('data', [ qw(netz name) ]);
84
85 my $save_session = sub
86 {
87 my ($session) = @_;
88
89 open(my $fh, '>', $session_file) or _die "Cannot open $session_file for writing: $!\n";
90 print {$fh} "$session\n";
91 close($fh);
92 };
93
94 my $get_session = sub
95 {
96 open(my $fh, '<', $session_file) or _die "Cannot open $session_file for reading: $!\nPerhaps try running --init\n";
97 my $session = do { local $/; <$fh> };
98 chomp $session;
99 close($fh);
100
101 return $session;
102 };
103
104 my $session = $opts{i} ? substr(md5_hex(md5_hex(time() . {} . rand() . $$)), 0, 32) : $get_session->();
105
106 my %params = (
107 netz => $netz,
108 pc => hostname(),
109 name => $name,
110 debug => $opts{d} || false,
111 init => $opts{i} || false,
112 list => $opts{l} || false,
113 session => $session,
114 );
115
116 my $ua = LWP::UserAgent->new;
117
118 my $response = $ua->post($server_url, \%params);
119
120 if ($response->is_success) {
121 my $data;
122
123 eval {
124 $data = decode_json($response->decoded_content);
125 } or exit;
126
127 die "$0: [server] $data->{error}" if defined $data->{error};
128
129 if ($opts{i}) {
130 $save_session->($session);
131 }
132 elsif ($opts{l}) {
133 format STDOUT_TOP =
134 IP Name PC Netz Aktualisiert
135 ====================================================================================================
136 .
137 foreach my $entry (sort { $a->{netz} cmp $b->{netz} } @{$data->{entries}}) {
138 my $updated = strftime '%Y-%m-%d %H:%M:%S', localtime $entry->{time};
139 format STDOUT =
140 @<<<<<<<<<<<<<< @<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<
141 @$entry{qw(ip name pc netz)}, $updated
142 .
143 write;
144 }
145 }
146 else {
147 my %list;
148 foreach my $entry (@{$data->{entries}}) {
149 my $host = "$entry->{ip}\t" . join '.', @$entry{qw(name pc netz)};
150 push @{$list{$entry->{netz}}}, $host;
151 }
152
153 my $o = tie my @hosts, 'Tie::File', $hosts_file or _die "Cannot tie $hosts_file: $!\n";
154 $o->flock(LOCK_EX);
155
156 foreach my $network (keys %list) {
157 my %indexes;
158 for (my $i = 0; $i < @hosts; $i++) {
159 if ($hosts[$i] =~ /^\#$network\#$/i) {
160 $indexes{start} = $i;
161 }
162 elsif (exists $indexes{start} && $hosts[$i] =~ /^\#\/$network\#$/i) {
163 $indexes{end} = $i;
164 my $count = ($indexes{end} - $indexes{start} > 1)
165 ? $indexes{end} - $indexes{start} - 1
166 : 0;
167 splice @hosts, $indexes{start} + 1, $count, @{$list{$network}};
168 last;
169 }
170 }
171 }
172
173 undef $o;
174 untie @hosts;
175 }
176 }
177 else {
178 warn $response->status_line, "\n";
179 }