File Coverage

blib/lib/FusionInventory/Agent/HTTP/Server.pm
Criterion Covered Total %
statement 36 178 20.2
branch 0 44 0.0
condition 0 5 0.0
subroutine 12 21 57.1
pod 3 3 100.0
total 51 251 20.3


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