File Coverage

blib/lib/FusionInventory/Agent/HTTP/Server.pm
Criterion Covered Total %
statement 88 178 49.4
branch 13 44 29.5
condition 3 5 60.0
subroutine 18 21 85.7
pod 3 3 100.0
total 125 251 49.8


line stmt bran cond sub pod time code
1             package FusionInventory::Agent::HTTP::Server;
2              
3 5     5   16733127 use strict;
  5         19  
  5         198  
4 5     5   39 use warnings;
  5         11  
  5         284  
5              
6 5     5   36 use English qw(-no_match_vars);
  5         131  
  5         69  
7 5     5   3499 use File::Basename;
  5         11  
  5         571  
8 5     5   9642 use HTTP::Daemon;
  5         252217  
  5         72  
9 5     5   4539 use IO::Handle;
  5         18  
  5         206  
10 5     5   5804 use Net::IP;
  5         247017  
  5         956  
11 5     5   5831 use Text::Template;
  5         18335  
  5         268  
12 5     5   36 use File::Glob;
  5         10  
  5         266  
13 5     5   34 use URI;
  5         10  
  5         66  
14              
15 5     5   798 use FusionInventory::Agent::Logger;
  5         11  
  5         358  
16 5     5   3166 use FusionInventory::Agent::Tools::Network;
  5         22  
  5         11143  
17              
18             my $log_prefix = "[http server] ";
19              
20             sub new {
21 13     13 1 9159 my ($class, %params) = @_;
22              
23             my $self = {
24             logger => $params{logger} ||
25             FusionInventory::Agent::Logger->new(),
26             agent => $params{agent},
27             htmldir => $params{htmldir},
28             ip => $params{ip},
29 13   33     248 port => $params{port} || 62354,
      100        
30             };
31 13         39 bless $self, $class;
32              
33             # compute addresses allowed for push requests
34 13         142 foreach my $target ($self->{agent}->getTargets()) {
35 0 0       0 next unless $target->isa('FusionInventory::Agent::Target::Server');
36 0         0 my $url = $target->getUrl();
37 0         0 my $host = URI->new($url)->host();
38 0         0 my @addresses = compile($host, $self->{logger});
39 0         0 $self->{trust}->{$url} = \@addresses;
40             }
41 13 100       51 if ($params{trust}) {
42 6         18 foreach my $string (@{$params{trust}}) {
  6         21  
43 15         75 my @addresses = compile($string);
44 15 100       11706 $self->{trust}->{$string} = \@addresses if @addresses;
45             }
46             }
47              
48 13         163 return $self;
49             }
50              
51             sub _handle {
52 6     6   25 my ($self, $client, $request, $clientIp) = @_;
53              
54 6         50 my $logger = $self->{logger};
55              
56 6 50       50 if (!$request) {
57 0         0 $client->close();
58 0         0 return;
59             }
60              
61 6         34 my $path = $request->uri()->path();
62 6         399 $logger->debug($log_prefix . "request $path from client $clientIp");
63              
64 6         29 my $method = $request->method();
65 6         109 my $status;
66 6 50       36 if ($method ne 'GET') {
67 0         0 $logger->error($log_prefix . "invalid request type: $method");
68 0         0 $client->send_error(400);
69 0         0 $status = 400;
70             } else {
71             SWITCH: {
72             # root request
73 6 50       14 if ($path eq '/') {
  6         28  
74 6         38 $status = $self->_handle_root($client, $request, $clientIp);
75 6         24 last SWITCH;
76             }
77              
78             # deploy request
79 0 0       0 if ($path =~ m{^/deploy/getFile/./../([\w\d/-]+)$}) {
80 0         0 $status = $self->_handle_deploy($client, $request, $clientIp, $1);
81 0         0 last SWITCH;
82             }
83              
84             # now request
85 0 0       0 if ($path =~ m{^/now(?:/(\S*))?$}) {
86 0         0 $status = $self->_handle_now($client, $request, $clientIp, $1);
87 0         0 last SWITCH;
88             }
89              
90             # status request
91 0 0       0 if ($path eq '/status') {
92 0         0 $status = $self->_handle_status($client, $request, $clientIp);
93 0         0 last SWITCH;
94             }
95              
96             # static content request
97 0 0       0 if ($path =~ m{^/(logo.png|site.css|favicon.ico)$}) {
98 0         0 my $file = $1;
99 0         0 $client->send_file_response("$self->{htmldir}/$file");
100 0         0 $status = 200;
101 0         0 last SWITCH;
102             }
103              
104 0         0 $logger->error($log_prefix . "unknown path: $path");
105 0         0 $client->send_error(400);
106 0         0 $status = 400;
107             }
108             }
109              
110 6         56 $logger->debug($log_prefix . "response status $status");
111              
112 6         75 $client->close();
113             }
114              
115             sub _handle_root {
116 6     6   20 my ($self, $client, $request, $clientIp) = @_;
117              
118 6         15 my $logger = $self->{logger};
119              
120 6         382 my $template = Text::Template->new(
121             TYPE => 'FILE', SOURCE => "$self->{htmldir}/index.tpl"
122             );
123 6 50       2863 if (!$template) {
124 0         0 $logger->error(
125             $log_prefix . "Template access failed: $Text::Template::ERROR"
126             );
127              
128 0         0 my $response = HTTP::Response->new(
129             500,
130             'KO',
131             HTTP::Headers->new('Content-Type' => 'text/html'),
132             "No template"
133             );
134              
135 0         0 $client->send_response($response);
136 0         0 return 500;
137             }
138              
139             my @server_targets =
140 0         0 map { { name => $_->getUrl(), date => $_->getFormatedNextRunDate() } }
141 0         0 grep { $_->isa('FusionInventory::Agent::Target::Server') }
142 6         164 $self->{agent}->getTargets();
143              
144             my @local_targets =
145 0         0 map { { name => $_->getPath(), date => $_->getFormatedNextRunDate() } }
146 0         0 grep { $_->isa('FusionInventory::Agent::Target::Local') }
147 6         35 $self->{agent}->getTargets();
148              
149             my $hash = {
150             version => $FusionInventory::Agent::VERSION,
151             trust => $self->_isTrusted($clientIp),
152 6         79 status => $self->{agent}->getStatus(),
153             server_targets => \@server_targets,
154             local_targets => \@local_targets
155             };
156              
157 6         77 my $response = HTTP::Response->new(
158             200,
159             'OK',
160             HTTP::Headers->new('Content-Type' => 'text/html'),
161             $template->fill_in(HASH => $hash)
162             );
163              
164 6         2477 $client->send_response($response);
165 6         4069 return 200;
166             }
167              
168             sub _handle_deploy {
169 0     0   0 my ($self, $client, $request, $clientIp, $sha512) = @_;
170              
171 0 0       0 return unless $sha512 =~ /^(.)(.)(.{6})/;
172 0         0 my $subFilePath = $1.'/'.$2.'/'.$3;
173              
174 0         0 Digest::SHA->require();
175              
176 0         0 my $path;
177 0         0 LOOP: foreach my $target ($self->{agent}->getTargets()) {
178 0         0 foreach (File::Glob::glob($target->{storage}->getDirectory() . "/deploy/fileparts/shared/*")) {
179 0 0       0 next unless -f $_.'/'.$subFilePath;
180              
181 0         0 my $sha = Digest::SHA->new('512');
182 0         0 $sha->addfile($_.'/'.$subFilePath, 'b');
183 0 0       0 next unless $sha->hexdigest eq $sha512;
184              
185 0         0 $path = $_.'/'.$subFilePath;
186 0         0 last LOOP;
187             }
188             }
189 0 0       0 if ($path) {
190 0         0 $client->send_file_response($path);
191 0         0 return 200;
192             } else {
193 0         0 $client->send_error(404);
194 0         0 return 404;
195             }
196             }
197              
198             sub _handle_now {
199 0     0   0 my ($self, $client, $request, $clientIp) = @_;
200              
201 0         0 my $logger = $self->{logger};
202              
203 0         0 my ($code, $message, $trace);
204              
205             BLOCK: {
206 0         0 foreach my $target ($self->{agent}->getTargets()) {
  0         0  
207 0 0       0 next unless $target->isa('FusionInventory::Agent::Target::Server');
208 0         0 my $url = $target->getUrl();
209 0         0 my $addresses = $self->{trust}->{$url};
210 0 0       0 next unless isPartOf($clientIp, $addresses, $logger);
211 0         0 $target->setNextRunDate(1);
212 0         0 $code = 200;
213 0         0 $message = "OK";
214 0         0 $trace = "rescheduling next contact for target $url right now";
215 0         0 last BLOCK;
216             }
217              
218 0 0       0 if ($self->_isTrusted($clientIp)) {
219 0         0 foreach my $target ($self->{agent}->getTargets()) {
220 0         0 $target->setNextRunDate(1);
221             }
222 0         0 $code = 200;
223 0         0 $message = "OK";
224 0         0 $trace = "rescheduling next contact for all targets right now";
225 0         0 last BLOCK;
226             }
227              
228 0         0 $code = 403;
229 0         0 $message = "Access denied";
230 0         0 $trace = "invalid request (untrusted address)";
231             }
232              
233 0         0 my $template = Text::Template->new(
234             TYPE => 'FILE', SOURCE => "$self->{htmldir}/now.tpl"
235             );
236              
237 0         0 my $hash = {
238             message => $message
239             };
240              
241 0         0 my $response = HTTP::Response->new(
242             $code,
243             'OK',
244             HTTP::Headers->new('Content-Type' => 'text/html'),
245             $template->fill_in(HASH => $hash)
246             );
247              
248 0         0 $client->send_response($response);
249 0         0 $logger->debug($log_prefix . $trace);
250 0         0 return $code;
251             }
252              
253             sub _handle_status {
254 0     0   0 my ($self, $client, $request, $clientIp) = @_;
255              
256 0         0 my $status = $self->{agent}->getStatus();
257 0         0 my $response = HTTP::Response->new(
258             200,
259             'OK',
260             HTTP::Headers->new('Content-Type' => 'text/plain'),
261             "status: ".$status
262             );
263 0         0 $client->send_response($response);
264 0         0 return 200;
265             }
266              
267             sub _isTrusted {
268 22     22   2841 my ($self, $address) = @_;
269              
270 22         59 foreach my $trusted_addresses (values %{$self->{trust}}) {
  22         197  
271             return 1
272             if isPartOf(
273             $address,
274             $trusted_addresses,
275             $self->{logger}
276 18 100       153 );
277             }
278              
279 13         196 return 0;
280             }
281              
282             sub init {
283 7     7 1 5455 my ($self) = @_;
284              
285 7         24 my $logger = $self->{logger};
286              
287             $self->{listener} = HTTP::Daemon->new(
288             LocalAddr => $self->{ip},
289             LocalPort => $self->{port},
290 7         123 Reuse => 1,
291             Timeout => 5
292             );
293              
294 7 50       3098 if (!$self->{listener}) {
295 0         0 $logger->error($log_prefix . "failed to start the HTTPD service");
296 0         0 return;
297             }
298              
299             $logger->debug(
300 7         61 $log_prefix . "HTTPD service started on port $self->{port}"
301             );
302             }
303              
304             sub handleRequests {
305 6     6 1 1688643 my ($self) = @_;
306              
307 6 50       106 return unless $self->{listener}; # init() call failed
308              
309 6         187 my ($client, $socket) = $self->{listener}->accept();
310 6 50       260140 return unless $socket;
311              
312 6         210 my (undef, $iaddr) = sockaddr_in($socket);
313 6         314 my $clientIp = inet_ntoa($iaddr);
314 6         247 my $request = $client->get_request();
315 6         339442 $self->_handle($client, $request, $clientIp);
316             }
317              
318             1;
319             __END__