File Coverage

blib/lib/FusionInventory/Agent/HTTP/Server.pm
Criterion Covered Total %
statement 90 185 48.6
branch 13 46 28.2
condition 3 5 60.0
subroutine 18 21 85.7
pod 3 3 100.0
total 127 260 48.8


line stmt bran cond sub pod time code
1             package FusionInventory::Agent::HTTP::Server;
2              
3 5     5   15272826 use strict;
  5         16  
  5         144  
4 5     5   22 use warnings;
  5         9  
  5         159  
5              
6 5     5   22 use English qw(-no_match_vars);
  5         36  
  5         30  
7 5     5   1664 use File::Basename;
  5         7  
  5         413  
8 5     5   2355 use HTTP::Daemon;
  5         119765  
  5         40  
9 5     5   1956 use IO::Handle;
  5         6  
  5         138  
10 5     5   2953 use Net::IP;
  5         143551  
  5         559  
11 5     5   2666 use Text::Template;
  5         10992  
  5         186  
12 5     5   24 use File::Glob;
  5         6  
  5         181  
13 5     5   15 use URI;
  5         5  
  5         34  
14              
15 5     5   395 use FusionInventory::Agent::Logger;
  5         6  
  5         373  
16 5     5   1766 use FusionInventory::Agent::Tools::Network;
  5         9  
  5         7114  
17              
18             my $log_prefix = "[http server] ";
19              
20             sub new {
21 13     13 1 4521 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     138 port => $params{port} || 62354,
      100        
30             };
31 13         20 bless $self, $class;
32              
33             # compute addresses allowed for push requests
34 13         80 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       37 if ($params{trust}) {
42 6         9 foreach my $string (@{$params{trust}}) {
  6         12  
43 15         45 my @addresses = compile($string);
44 15 100       6660 $self->{trust}->{$string} = \@addresses if @addresses;
45             }
46             }
47              
48 13         88 return $self;
49             }
50              
51             sub _handle {
52 6     6   18 my ($self, $client, $request, $clientIp) = @_;
53              
54 6         28 my $logger = $self->{logger};
55              
56 6 50       32 if (!$request) {
57 0         0 $client->close();
58 0         0 return;
59             }
60              
61 6         36 my $path = $request->uri()->path();
62 6         240 $logger->debug($log_prefix . "request $path from client $clientIp");
63              
64 6         29 my $method = $request->method();
65 6         48 my $status;
66 6 50       27 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       9 if ($path eq '/') {
  6         23  
74 6         65 $status = $self->_handle_root($client, $request, $clientIp);
75 6         17 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         36 $logger->debug($log_prefix . "response status $status");
111              
112 6         53 $client->close();
113             }
114              
115             sub _handle_root {
116 6     6   18 my ($self, $client, $request, $clientIp) = @_;
117              
118 6         21 my $logger = $self->{logger};
119              
120 6         205 my $template = Text::Template->new(
121             TYPE => 'FILE', SOURCE => "$self->{htmldir}/index.tpl"
122             );
123 6 50       1832 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         143 $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         27 $self->{agent}->getTargets();
148              
149             my $hash = {
150             version => $FusionInventory::Agent::VERSION,
151             trust => $self->_isTrusted($clientIp),
152 6         47 status => $self->{agent}->getStatus(),
153             server_targets => \@server_targets,
154             local_targets => \@local_targets
155             };
156              
157 6         56 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         1513 $client->send_response($response);
165 6         2552 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 my $logger = $self->{logger};
175              
176 0         0 Digest::SHA->require();
177 0 0       0 if ($EVAL_ERROR) {
178 0         0 $logger->error("Failed to load Digest::SHA: $EVAL_ERROR");
179             # Return 501 (Not Implemented) to client with reason
180 0         0 $client->send_error(501, 'Digest::SHA perl library is missing');
181 0         0 return 501;
182             }
183              
184 0         0 my $path;
185 0         0 LOOP: foreach my $target ($self->{agent}->getTargets()) {
186 0         0 foreach (File::Glob::glob($target->{storage}->getDirectory() . "/deploy/fileparts/shared/*")) {
187 0 0       0 next unless -f $_.'/'.$subFilePath;
188              
189 0         0 my $sha = Digest::SHA->new('512');
190 0         0 $sha->addfile($_.'/'.$subFilePath, 'b');
191 0 0       0 next unless $sha->hexdigest eq $sha512;
192              
193 0         0 $path = $_.'/'.$subFilePath;
194 0         0 last LOOP;
195             }
196             }
197 0 0       0 if ($path) {
198 0         0 $client->send_file_response($path);
199 0         0 return 200;
200             } else {
201 0         0 $client->send_error(404);
202 0         0 return 404;
203             }
204             }
205              
206             sub _handle_now {
207 0     0   0 my ($self, $client, $request, $clientIp) = @_;
208              
209 0         0 my $logger = $self->{logger};
210              
211 0         0 my ($code, $message, $trace);
212              
213             BLOCK: {
214 0         0 foreach my $target ($self->{agent}->getTargets()) {
  0         0  
215 0 0       0 next unless $target->isa('FusionInventory::Agent::Target::Server');
216 0         0 my $url = $target->getUrl();
217 0         0 my $addresses = $self->{trust}->{$url};
218 0 0       0 next unless isPartOf($clientIp, $addresses, $logger);
219 0         0 $target->setNextRunDate(1);
220 0         0 $code = 200;
221 0         0 $message = "OK";
222 0         0 $trace = "rescheduling next contact for target $url right now";
223 0         0 last BLOCK;
224             }
225              
226 0 0       0 if ($self->_isTrusted($clientIp)) {
227 0         0 foreach my $target ($self->{agent}->getTargets()) {
228 0         0 $target->setNextRunDate(1);
229             }
230 0         0 $code = 200;
231 0         0 $message = "OK";
232 0         0 $trace = "rescheduling next contact for all targets right now";
233 0         0 last BLOCK;
234             }
235              
236 0         0 $code = 403;
237 0         0 $message = "Access denied";
238 0         0 $trace = "invalid request (untrusted address)";
239             }
240              
241 0         0 my $template = Text::Template->new(
242             TYPE => 'FILE', SOURCE => "$self->{htmldir}/now.tpl"
243             );
244              
245 0         0 my $hash = {
246             message => $message
247             };
248              
249 0         0 my $response = HTTP::Response->new(
250             $code,
251             'OK',
252             HTTP::Headers->new('Content-Type' => 'text/html'),
253             $template->fill_in(HASH => $hash)
254             );
255              
256 0         0 $client->send_response($response);
257 0         0 $logger->debug($log_prefix . $trace);
258 0         0 return $code;
259             }
260              
261             sub _handle_status {
262 0     0   0 my ($self, $client, $request, $clientIp) = @_;
263              
264 0         0 my $status = $self->{agent}->getStatus();
265 0         0 my $response = HTTP::Response->new(
266             200,
267             'OK',
268             HTTP::Headers->new('Content-Type' => 'text/plain'),
269             "status: ".$status
270             );
271 0         0 $client->send_response($response);
272 0         0 return 200;
273             }
274              
275             sub _isTrusted {
276 22     22   803 my ($self, $address) = @_;
277              
278 22         26 foreach my $trusted_addresses (values %{$self->{trust}}) {
  22         98  
279             return 1
280             if isPartOf(
281             $address,
282             $trusted_addresses,
283             $self->{logger}
284 18 100       54 );
285             }
286              
287 13         130 return 0;
288             }
289              
290             sub init {
291 7     7 1 1955 my ($self) = @_;
292              
293 7         14 my $logger = $self->{logger};
294              
295             $self->{listener} = HTTP::Daemon->new(
296             LocalAddr => $self->{ip},
297             LocalPort => $self->{port},
298 7         315 Reuse => 1,
299             Timeout => 5,
300             Blocking => 0
301             );
302              
303 7 50       2230 if (!$self->{listener}) {
304 0         0 $logger->error($log_prefix . "failed to start the HTTPD service");
305 0         0 return;
306             }
307              
308             $logger->debug(
309 7         42 $log_prefix . "HTTPD service started on port $self->{port}"
310             );
311              
312 7         14 return 1;
313             }
314              
315             sub handleRequests {
316 6     6 1 1117824 my ($self) = @_;
317              
318 6 50       67 return unless $self->{listener}; # init() call failed
319              
320 6         111 my ($client, $socket) = $self->{listener}->accept();
321 6 50       147913 return unless $socket;
322              
323 6         114 my (undef, $iaddr) = sockaddr_in($socket);
324 6         147 my $clientIp = inet_ntoa($iaddr);
325 6         93 my $request = $client->get_request();
326 6         198869 $self->_handle($client, $request, $clientIp);
327              
328 6         405 return 1;
329             }
330              
331             1;
332             __END__