File Coverage

blib/lib/Onyphe/Api.pm
Criterion Covered Total %
statement 30 1132 2.6
branch 0 420 0.0
condition 0 197 0.0
subroutine 10 88 11.3
pod 0 59 0.0
total 40 1896 2.1


line stmt bran cond sub pod time code
1             #
2             # $Id: Api.pm,v 184b7baee9fd 2025/03/14 14:27:03 gomor $
3             #
4             package Onyphe::Api;
5 1     1   1226 use strict;
  1         2  
  1         42  
6 1     1   7 use warnings;
  1         2  
  1         95  
7              
8             our $VERSION = '4.19.0';
9              
10 1     1   7 use experimental qw(signatures);
  1         2  
  1         8  
11              
12 1     1   185 use base qw(Onyphe);
  1         2  
  1         217  
13              
14             our @AS = qw(endpoint apikey username password);
15             __PACKAGE__->cgBuildAccessorsScalar(\@AS);
16             __PACKAGE__->cgBuildIndices;
17              
18             #use utf8;
19 1     1   8 use File::Temp qw(tempfile);
  1         2  
  1         78  
20 1     1   7 use File::Slurp qw(read_file);
  1         2  
  1         51  
21 1     1   895 use Mojo::URL;
  1         229767  
  1         9  
22 1     1   1232 use JSON::XS qw(encode_json decode_json);
  1         9113  
  1         99  
23 1     1   881 use Mojo::UserAgent;
  1         367316  
  1         10  
24 1     1   65 use Mojo::Util qw(b64_encode url_escape);
  1         2  
  1         16734  
25              
26             #
27             # Common functions:
28             #
29 0     0     sub _ua ($self) {
  0            
  0            
30 0           my $ua = Mojo::UserAgent->new(
31             connect_timeout => 5,
32             max_response_size => 0,
33             inactivity_timeout => 0,
34             );
35              
36 0           $ua->transactor->name('Onyphe::Api-v'.$VERSION);
37 0           $ua->proxy->detect;
38              
39 0           return $ua;
40             }
41              
42 0     0     sub _headers ($self, $apikey, $ct = undef) {
  0            
  0            
  0            
  0            
43 0           my $headers = {
44             'Authorization' => 'Bearer '.$apikey,
45             'X-Api-Key' => $apikey, # Ready for APIv3
46             'Content-Type' => 'application/json',
47             'Accept' => '*/*',
48             };
49 0 0         if (defined($ct)) {
50 0           $headers->{'Content-Type'} = $ct;
51             }
52 0           my $global = $self->config->{''};
53 0   0       my $username = $global->{api_unrated_email} || $self->username;
54 0 0 0       if ($global->{api_unrated_endpoint} && $username) {
55             print STDERR "VERBOSE: Using Unrated API endpoint: ".$global->{api_unrated_endpoint}.
56 0 0         ", with username: $username\n" if $self->verbose;
57 0 0 0       if (!defined($username) || !defined($apikey)) {
58 0 0         print STDERR "ERROR: need api_unrated_email & api_key settings\n"
59             unless $self->silent;
60 0           return;
61             }
62 0           $username =~ s{\@}{_};
63 0           my $auth = b64_encode($username.':'.$apikey, '');
64 0           $headers->{Authorization} = 'Basic '.$auth;
65             }
66 0           return $headers;
67             }
68              
69 0     0 0   sub get_total ($self, $json) {
  0            
  0            
  0            
70 0           my $total = $json->{total};
71 0 0 0       return defined($total) && $total ? $total : 0;
72             }
73              
74 0     0 0   sub get_maxpage ($self, $json) {
  0            
  0            
  0            
75 0           my $maxpage = $json->{max_page};
76 0 0 0       return defined($maxpage) && $maxpage ? $maxpage : 0;
77             }
78              
79 0     0 0   sub get_results ($self, $json) {
  0            
  0            
  0            
80 0           my $results = $json->{results};
81 0 0 0       return defined($results) && @$results ? $results : [];
82             }
83              
84 0     0 0   sub encode ($self, $result) {
  0            
  0            
  0            
85 0 0         return '' unless $result;
86 0           my $encode;
87 0           eval {
88 0           $encode = encode_json($result);
89             };
90 0 0         if ($@) {
91 0           chomp($@);
92 0 0         print STDERR "ERROR: encode failed: $@\n" unless $self->silent;
93 0           return '';
94             }
95 0           return $encode;
96             }
97              
98             #
99             # Request-based APIs
100             #
101 0     0     sub _cb_request ($self, $results = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
102 0     0     return sub ($results, $cb_args) {
  0            
  0            
  0            
103 0 0         $results = ref($results) eq 'ARRAY' ? $results : [ $results ];
104 0           for (@$results) {
105 0 0 0       next if defined($_->{'@category'}) && $_->{'@category'} eq 'none';
106 0           print $self->encode($_)."\n";
107             }
108 0           };
109             }
110              
111 0     0     sub _params ($self, $params) {
  0            
  0            
  0            
112 0           my $first = 1;
113 0           my $args = '';
114 0           for my $p (@$params) {
115 0           my $op = '?';
116 0 0         $op = '&' unless $first;
117 0           $args .= $op.$p->{k}.'='.$p->{v};
118 0           $first = 0;
119             }
120 0           return $args;
121             }
122              
123             # $self->request('/search', 'protocol:ssh', 1, 10);
124             # $self->request('/simple/datascan', '8.8.8.8');
125 0     0 0   sub request ($self, $api, $input = undef, $page = undef, $maxpage = undef, $params = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
126 0           my $global = $self->config->{''};
127             my $endpoint = $global->{api_unrated_endpoint} || $global->{api_endpoint}
128 0   0       || $self->endpoint;
129 0   0       my $apikey = $global->{api_key} || $self->apikey;
130              
131 0 0         die("FATAL: api_key not configured in ~/.onyphe.ini\n") unless defined($apikey);
132 0 0         die("FATAL: api_endpoint not configured in ~/.onyphe.ini\n") unless defined($endpoint);
133              
134             # Use default callback when none given:
135 0   0       $cb ||= $self->_cb_request;
136              
137 0   0       $page ||= 1;
138 0   0       $maxpage ||= $global->{api_maxpage} || 1;
      0        
139              
140 0           my $ua = $self->_ua();
141 0           my $headers = $self->_headers($apikey);
142              
143 0           $api =~ s{^/*}{/}g;
144 0           my $this_max_page;
145 0           while (1) {
146 0           my $path = $endpoint.$api;
147 0 0         $path .= '/'.url_escape($input) if defined $input;
148 0           my $p = [];
149 0           push @$p, { k => 'k', v => $apikey };
150 0 0         push @$p, { k => 'page', v => $page } if defined $page;
151 0 0         push @$p, { k => 'trackquery', v => 'true' } if $global->{api_trackquery};
152 0 0         push @$p, { k => 'calculated', v => 'true' } if $global->{api_calculated};
153 0 0         push @$p, { k => 'keepalive', v => 'true' } if $global->{api_keepalive};
154 0 0 0       if (defined($params) && ref($params) eq 'HASH' && keys %$params) {
      0        
155 0           for my $this (qw(size trackquery calculated keepalive)) {
156 0 0 0       next unless defined($params->{$this}) || defined($params->{'api_'.$this});
157 0   0       push @$p, { k => $this, v => $params->{$this} || $global->{'api_'.$this} };
158             }
159             }
160 0           my $args = $self->_params($p);
161 0 0         $path .= $args if $args;
162 0           my $url = Mojo::URL->new($path);
163              
164 0 0         print STDERR "VERBOSE: Calling API: $path\n" if $self->verbose;
165              
166 0           RETRY:
167             my $res;
168 0           eval {
169 0           $res = $ua->get($url => $headers)->result;
170             };
171 0 0         if ($@) {
172 0           chomp($@);
173 0 0         print STDERR "WARNING: Request API call failed: [$@], retrying...\n" unless $self->silent;
174 0           goto RETRY;
175             }
176 0 0         unless ($res->is_success) {
177 0           my $code = $res->code;
178             # Not JSON result:
179 0 0         unless (defined($res->json)) {
180 0           my $text = $res->message;
181 0           print STDERR "ERROR: Request API call failed: $code, $text\n";
182 0           return;
183             }
184 0           my $json = $res->json;
185             # If code 429, retry with some sleep:
186 0 0         if ($code == 429) {
187 0 0         print STDERR "WARNING: Too fast, sleeping before retry...\n" unless $self->silent;
188 0           sleep 1;
189 0           goto RETRY;
190             }
191             # Otherwise, stops and display error:
192 0 0         print STDERR "ERROR: Request API call failed: $code, ".encode_json($json)."\n"
193             unless $self->silent;
194 0           return;
195             }
196              
197 0           my $json = $res->json;
198              
199             # When asking for a count only, display and stop:
200 0 0 0       if (defined($params) && $params->{count}) {
201 0           my $total = $self->get_total($json);
202 0           $cb->([{ "total" => $total } ], $cb_args);
203 0           return 1;
204             }
205              
206             # Fetch max_page value so we can iterate:
207 0 0         $this_max_page = $self->get_maxpage($json) unless defined $this_max_page;
208 0 0 0       if (defined($input) && !$this_max_page) {
209 0 0         print STDERR "ERROR: Request API call failed, no max_page found\n"
210             unless $self->silent;
211 0 0         print STDERR "VERBOSE: ".Data::Dumper::Dumper($json)."\n" if $self->verbose;
212 0           return;
213             }
214              
215 0           my $results = $self->get_results($json);
216 0 0 0       if (defined($input) && !@$results) {
217 0 0         print STDERR "ERROR: Request API call failed, no results found\n"
218             unless $self->silent;
219 0 0         print STDERR "VERBOSE: ".Data::Dumper::Dumper($json)."\n" if $self->verbose;
220 0           return;
221             }
222 0           $cb->($results, $cb_args);
223              
224 0 0 0       last unless (defined($page) && defined($maxpage));
225              
226 0 0 0       last if ($page == $maxpage || $page >= $this_max_page);
227 0           $page++;
228             }
229              
230 0           return 1;
231             }
232              
233             # $self->post_request('/search', 'protocol:ssh', 1, 10);
234             # $self->post_request('/simple/datascan', '8.8.8.8');
235 0     0 0   sub post_request ($self, $api, $input = undef, $page = undef, $maxpage = undef, $params = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
236 0           my $global = $self->config->{''};
237             my $endpoint = $global->{api_unrated_endpoint} || $global->{api_endpoint}
238 0   0       || $self->endpoint;
239 0   0       my $apikey = $global->{api_key} || $self->apikey;
240              
241 0 0         die("FATAL: api_key not configured in ~/.onyphe.ini\n") unless defined($apikey);
242 0 0         die("FATAL: api_endpoint not configured in ~/.onyphe.ini\n") unless defined($endpoint);
243              
244             # Use default callback when none given:
245 0   0       $cb ||= $self->_cb_request;
246              
247 0   0       $page ||= 1;
248 0   0       $maxpage ||= $global->{api_maxpage} || 1;
      0        
249              
250 0           my $ua = $self->_ua();
251 0           my $headers = $self->_headers($apikey, 'application/x-www-form-urlencoded');
252              
253 0           $api =~ s{^/*}{/}g;
254 0           my $this_max_page;
255 0           while (1) {
256 0           my $path = $endpoint.$api;
257 0           my $p = [];
258 0           push @$p, { k => 'k', v => $apikey };
259 0 0         push @$p, { k => 'page', v => $page } if defined $page;
260 0 0         push @$p, { k => 'trackquery', v => 'true' } if $global->{api_trackquery};
261 0 0         push @$p, { k => 'calculated', v => 'true' } if $global->{api_calculated};
262 0 0         push @$p, { k => 'keepalive', v => 'true' } if $global->{api_keepalive};
263 0 0 0       if (defined($params) && ref($params) eq 'HASH' && keys %$params) {
      0        
264 0           for my $this (qw(size trackquery calculated keepalive)) {
265 0 0 0       next unless defined($params->{$this}) || defined($params->{'api_'.$this});
266 0   0       push @$p, { k => $this, v => $params->{$this} || $global->{'api_'.$this} };
267             }
268             }
269 0           my $args = $self->_params($p);
270 0 0         $path .= $args if $args;
271 0           my $url = Mojo::URL->new($path);
272              
273 0 0         print STDERR "VERBOSE: Calling API: $path\n" if $self->verbose;
274              
275 0           RETRY:
276             my $res;
277 0           eval {
278 0           $res = $ua->post($url => $headers => form => { query => $input })->result;
279             };
280 0 0         if ($@) {
281 0           chomp($@);
282 0 0         print STDERR "WARNING: Request API call failed: [$@], retrying...\n" unless $self->silent;
283 0           goto RETRY;
284             }
285 0 0         unless ($res->is_success) {
286 0           my $code = $res->code;
287             # Not JSON result:
288 0 0         unless (defined($res->json)) {
289 0           my $text = $res->message;
290 0           print STDERR "ERROR: Request API call failed: $code, $text\n";
291 0           return;
292             }
293 0           my $json = $res->json;
294             # If code 429, retry with some sleep:
295 0 0         if ($code == 429) {
296 0 0         print STDERR "WARNING: Too fast, sleeping before retry...\n" unless $self->silent;
297 0           sleep 1;
298 0           goto RETRY;
299             }
300             # Otherwise, stops and display error:
301 0 0         print STDERR "ERROR: Request API call failed: $code, ".encode_json($json)."\n"
302             unless $self->silent;
303 0           return;
304             }
305              
306 0           my $json = $res->json;
307             # Fetch max_page value so we can iterate:
308 0 0         $this_max_page = $self->get_maxpage($json) unless defined $this_max_page;
309 0 0 0       if (defined($input) && !$this_max_page) {
310 0 0         print STDERR "ERROR: Request API call failed, no max_page found\n"
311             unless $self->silent;
312 0 0         print STDERR "VERBOSE: ".Data::Dumper::Dumper($json)."\n" if $self->verbose;
313 0           return;
314             }
315              
316 0           my $results = $self->get_results($json);
317 0 0 0       if (defined($input) && !@$results) {
318 0 0         print STDERR "ERROR: Request API call failed, no results found\n"
319             unless $self->silent;
320 0 0         print STDERR "VERBOSE: ".Data::Dumper::Dumper($json)."\n" if $self->verbose;
321 0           return;
322             }
323 0           $cb->($results, $cb_args);
324              
325 0 0 0       last unless (defined($page) && defined($maxpage));
326              
327 0 0 0       last if ($page == $maxpage || $page >= $this_max_page);
328 0           $page++;
329             }
330              
331 0           return 1;
332             }
333              
334             # $self->user();
335 0     0 0   sub user ($self, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
336 0           return $self->request('/user', undef, undef, undef, undef, $cb, $cb_args);
337             }
338              
339             # $self->summary('ip', '8.8.8.8');
340             # $self->summary('domain', 'example.com');
341             # $self->summary('hostname', 'www.example.com');
342 0     0 0   sub summary ($self, $api, $oql, $params = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
343 0           return $self->request('/summary/'.$api, $oql, undef, undef, $params, $cb, $cb_args);
344             }
345              
346             # $self->simple('datascan', 'Server: Apache');
347             # $self->simple('synscan', '8.8.8.8');
348 0     0 0   sub simple ($self, $api, $oql, $params = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
349 0           return $self->request('/simple/'.$api, $oql, undef, undef, $params, $cb, $cb_args);
350             }
351              
352             # $self->simple_best('geoloc', '8.8.8.8');
353             # $self->simple_best('inetnum', '8.8.8.8');
354             # $self->simple_best('threatlist', '8.8.8.8');
355             # $self->simple_best('whois', '8.8.8.8');
356 0     0 0   sub simple_best ($self, $api, $oql, $params = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
357 0           return $self->request('/simple/'.$api.'/best', $oql, undef, undef, $params, $cb, $cb_args);
358             }
359              
360             # $self->search('protocol:ssh', 1, 1000);
361 0     0 0   sub search ($self, $oql, $page = 1, $maxpage = 1, $params = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
362 0           return $self->request('/search', $oql, $page, $maxpage, $params, $cb, $cb_args);
363             }
364              
365 0     0 0   sub post_search ($self, $oql, $page = 1, $maxpage = 1, $params = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
366 0           return $self->post_request('/search', $oql, $page, $maxpage, $params, $cb, $cb_args);
367             }
368              
369             #
370             # Stream-based APIs
371             #
372 0     0     sub _cb_stream ($self, $results = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
373 0     0     return sub ($results, $cb_args) {
  0            
  0            
  0            
374 0 0         $results = ref($results) eq 'ARRAY' ? $results : [ $results ];
375 0           for (@$results) {
376 0 0         next if m{.\@category.\s*:\s*.none.};
377 0           print "$_\n";
378             }
379 0           };
380             }
381              
382 0     0     sub _on_read ($self, $cb = undef, $cb_args = undef, $buf = \'') {
  0            
  0            
  0            
  0            
  0            
383             return sub {
384 0     0     my ($content, $bytes) = @_;
385 0           $bytes = $$buf.$bytes; # Complete from previously incomplete lines
386 0           my ($this, $tail) = $bytes =~ m/^(.*\n)(.*)$/s;
387             # Check errors:
388 0 0 0       if (defined($bytes) && $bytes =~ m{"status":"nok"}) {
389 0           return $cb->($bytes, $cb_args);
390             }
391             # One line is not complete, add to buf and go to next:
392 0 0         if (!defined($this)) {
393 0           $buf = \$bytes;
394             }
395             else { # Lines complete, process them
396 0 0         $buf = defined($tail) ? \$tail : \'';
397 0           my $results = [ split(/\n/, $this) ];
398 0           return $cb->($results, $cb_args);
399             }
400 0           };
401             }
402              
403             # $self->stream('GET', '/export', 'protocol:ssh');
404             # $self->stream('POST', '/bulk/whois/best/ip', '/tmp/ip.txt');
405 0     0 0   sub stream ($self, $method, $api, $input, $params = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
406 0           my $global = $self->config->{''};
407             my $endpoint = $global->{api_unrated_endpoint} || $global->{api_endpoint}
408 0   0       || $self->endpoint;
409 0   0       my $apikey = $global->{api_key} || $self->apikey;
410              
411 0 0         die("FATAL: api_key not configured in ~/.onyphe.ini\n") unless defined($apikey);
412 0 0         die("FATAL: api_endpoint not configured in ~/.onyphe.ini\n") unless defined($endpoint);
413              
414             # Use default callback when none given:
415 0   0       $cb ||= $self->_cb_stream;
416              
417 0           my $ua = $self->_ua();
418 0           my $headers = $self->_headers($apikey);
419 0 0         if ($method eq 'POST') {
420 0           $headers = $self->_headers($apikey, 'application/x-www-form-urlencoded');
421             }
422              
423 0           my $path = $endpoint.$api;
424 0 0         unless (-f $input) {
425 0 0         if ($method eq 'GET') {
426 0           $path .= '/'.url_escape($input); # Build with OQL string
427             }
428             }
429              
430 0           my $p= [];
431 0           push @$p, { k => 'k', v => $apikey };
432 0 0         push @$p, { k => 'trackquery', v => 'true' } if $global->{api_trackquery};
433 0 0         push @$p, { k => 'calculated', v => 'true' } if $global->{api_calculated};
434 0 0         push @$p, { k => 'keepalive', v => 'true' } if $global->{api_keepalive};
435 0 0 0       if (defined($params) && ref($params) eq 'HASH' && keys %$params) {
      0        
436 0           for my $this (qw(size trackquery calculated keepalive)) {
437 0 0 0       next unless defined($params->{$this}) || defined($params->{'api_'.$this});
438 0   0       push @$p, { k => $this, v => $params->{$this} || $global->{'api_'.$this} };
439             }
440             }
441 0           my $args = $self->_params($p);
442 0 0         $path .= $args if $args;
443              
444 0 0         print STDERR "VERBOSE: Calling API: $path\n" if $self->verbose;
445              
446 0           my $url = Mojo::URL->new($path);
447              
448 0           my $buf = ''; # Will store incomplete lines for later processing
449 0           my $tx;
450 0 0         if ($method eq 'GET') {
    0          
451 0           $tx = $ua->build_tx($method => $url => $headers);
452             }
453             elsif ($method eq 'POST') {
454 0           $tx = $ua->build_tx($method => $url => $headers => form => { query => $input });
455             }
456             # Replace "read" events to disable default content parser:
457 0           $tx->res->content->unsubscribe('read')->on(read => $self->_on_read($cb, $cb_args, \$buf));
458              
459 0 0         if (-f $input) { # POST file content
460 0 0         print STDERR "VERBOSE: Reading file: $input\n" if $self->verbose;
461 0           $tx->req->content->asset(Mojo::Asset::File->new(path => $input));
462             }
463              
464             # Process transaction:
465 0           return $ua->start($tx);
466             }
467              
468 0     0 0   sub post_stream ($self, $method, $api, $input, $params = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
469 0           my $global = $self->config->{''};
470             my $endpoint = $global->{api_unrated_endpoint} || $global->{api_endpoint}
471 0   0       || $self->endpoint;
472 0   0       my $apikey = $global->{api_key} || $self->apikey;
473              
474 0 0         die("FATAL: api_key not configured in ~/.onyphe.ini\n") unless defined($apikey);
475 0 0         die("FATAL: api_endpoint not configured in ~/.onyphe.ini\n") unless defined($endpoint);
476              
477             # Use default callback when none given:
478 0   0       $cb ||= $self->_cb_stream;
479              
480 0           my $ua = $self->_ua();
481 0           my $headers = $self->_headers($apikey, 'application/x-www-form-urlencoded');
482              
483 0           my $path = $endpoint.$api;
484              
485 0           my $p = [];
486 0           push @$p, { k => 'k', v => $apikey };
487 0 0         push @$p, { k => 'trackquery', v => 'true' } if $global->{api_trackquery};
488 0 0         push @$p, { k => 'calculated', v => 'true' } if $global->{api_calculated};
489 0 0         push @$p, { k => 'keepalive', v => 'true' } if $global->{api_keepalive};
490 0 0 0       if (defined($params) && ref($params) eq 'HASH' && keys %$params) {
      0        
491 0           for my $this (qw(size trackquery calculated keepalive)) {
492 0 0 0       next unless defined($params->{$this}) || defined($params->{'api_'.$this});
493 0   0       push @$p, { k => $this, v => $params->{$this} || $global->{'api_'.$this} };
494             }
495             }
496 0           my $args = $self->_params($p);
497 0 0         $path .= $args if $args;
498              
499 0 0         print STDERR "VERBOSE: Calling API: $path\n" if $self->verbose;
500              
501 0           my $url = Mojo::URL->new($path);
502              
503 0           my $buf = ''; # Will store incomplete lines for later processing
504 0           my $tx = $ua->build_tx($method => $url => $headers => form => { query => $input });
505             # Replace "read" events to disable default content parser:
506 0           $tx->res->content->unsubscribe('read')->on(read => $self->_on_read($cb, $cb_args, \$buf));
507              
508 0 0         if (-f $input) { # POST file content
509 0 0         print STDERR "VERBOSE: Reading file: $input\n" if $self->verbose;
510 0           $tx->req->content->asset(Mojo::Asset::File->new(path => $input));
511             }
512              
513             # Process transaction:
514 0           return $ua->start($tx);
515             }
516              
517 0     0     sub _check_file ($self, $file) {
  0            
  0            
  0            
518 0 0         unless (-f $file) {
519 0 0         print STDERR "ERROR: file not found: $file\n" unless $self->silent;
520 0           return;
521             }
522 0           return $file;
523             }
524              
525             # $self->bulk_summary('ip', '/tmp/ip.txt');
526             # $self->bulk_summary('domain', '/tmp/domain.txt');
527             # $self->bulk_summary('hostname', '/tmp/hostname.txt');
528 0     0 0   sub bulk_summary ($self, $api, $file, $params = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
529 0 0         return unless $self->_check_file($file);
530 0           $api =~ s{^/*}{/}g;
531 0           return $self->stream('POST', '/bulk/summary'.$api, $file, $params, $cb, $cb_args);
532             }
533              
534             # $self->bulk_simple('ctl', '/tmp/ip.txt');
535             # $self->bulk_simple('resolver', '/tmp/ip.txt');
536 0     0 0   sub bulk_simple ($self, $api, $file, $params = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
537 0 0         return unless $self->_check_file($file);
538 0           $api =~ s{^/*}{/}g;
539 0           return $self->stream('POST', '/bulk/simple'.$api.'/ip', $file, $params, $cb, $cb_args);
540             }
541              
542             # $self->bulk_simple_best('threatlist', '/tmp/ip.txt');
543             # $self->bulk_simple_best('whois', '/tmp/ip.txt');
544 0     0 0   sub bulk_simple_best ($self, $api, $file, $params = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
545 0 0         return unless $self->_check_file($file);
546 0           $api =~ s{^/*}{/}g;
547 0           return $self->stream('POST', '/bulk/simple'.$api.'/best/ip', $file, $params, $cb, $cb_args);
548             }
549              
550             # $self->bulk_discovery('datascan', '/tmp/oql.txt');
551             # $self->bulk_discovery('domain', '/tmp/oql.txt');
552             # $self->bulk_discovery('domain', '/tmp/oql.txt', $oql);
553 0     0 0   sub bulk_discovery ($self, $api, $file, $oql = undef, $params = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
554 0 0         return unless $self->_check_file($file);
555 0           $api =~ s{^/*}{/}g;
556 0           my $input = $file;
557             # Rewrite to another file when oql is given:
558 0           my ($fh, $filename);
559 0 0         if (defined($oql)) {
560 0           my @lines = read_file($file);
561 0           ($fh, $filename) = tempfile();
562 0           $input = $filename;
563 0           for (@lines) {
564 0           chomp;
565 0           my $line = "$_ $oql";
566             #utf8::encode($line); # Not required, already in UTF-8
567 0           print $fh "$line\n";
568             }
569 0           close($fh);
570             }
571 0           $self->stream('POST', '/bulk/discovery'.$api.'/asset', $input, $params, $cb, $cb_args);
572 0 0 0       unlink($filename) if defined($filename) && -f $filename;
573 0           return 1;
574             }
575              
576             # $self->export('protocol:ssh');
577 0     0 0   sub export ($self, $oql, $params = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
578 0           return $self->stream('GET', '/export', $oql, $params, $cb, $cb_args);
579             }
580              
581 0     0 0   sub post_export ($self, $oql, $params = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
582 0           return $self->stream('POST', '/export', $oql, $params, $cb, $cb_args);
583             }
584              
585             #
586             # Alert API
587             #
588 0     0     sub _cb_alert ($self, $results = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
589 0     0     return sub ($results, $cb_args) {
  0            
  0            
  0            
590 0 0         $results = ref($results) eq 'ARRAY' ? $results : [ $results ];
591 0           for (@$results) {
592 0           print $self->encode($_)."\n";
593             }
594 0           };
595             }
596              
597 0     0 0   sub alert ($self, $method, $api, $name, $oql, $email, $threshold = 0, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
598 0           my $global = $self->config->{''};
599             my $endpoint = $global->{api_unrated_endpoint} || $global->{api_endpoint}
600 0   0       || $self->endpoint;
601 0   0       my $apikey = $global->{api_key} || $self->apikey;
602              
603 0 0         die("FATAL: api_key not configured in ~/.onyphe.ini\n") unless defined($apikey);
604 0 0         die("FATAL: api_endpoint not configured in ~/.onyphe.ini\n") unless defined($endpoint);
605              
606             # Use default callback when none given:
607 0   0       $cb ||= $self->_cb_alert;
608              
609 0           my $ua = $self->_ua();
610 0           my $headers = $self->_headers($apikey);
611              
612 0           $api =~ s{^/*}{/}g;
613              
614 0           my $path = $endpoint.$api;
615 0           $path .= '?k='.$apikey;
616              
617 0 0         print STDERR "VERBOSE: Calling API: $path\n" if $self->verbose;
618              
619 0           my $url = Mojo::URL->new($path);
620              
621 0           my $post;
622 0 0         $post->{name} = $name if defined $name;
623 0 0         $post->{query} = $oql if defined $oql;
624 0 0         $post->{email} = $email if defined $email;
625 0 0         $post->{threshold} = $threshold if defined $threshold;
626              
627 0           my @args = ( $url => $headers );
628 0 0         @args = ( $url => $headers => json => $post ) if defined $post;
629              
630             #print STDERR "DEBUG: args: ".Data::Dumper::Dumper(\@args)."\n";
631              
632 0           RETRY:
633             my $res;
634 0           eval {
635 0           $res = $ua->$method(@args)->result;
636             };
637 0 0         if ($@) {
638 0           chomp($@);
639 0 0         print STDERR "WARNING: Alert API call failed: [$@], retrying...\n" unless $self->silent;
640 0           goto RETRY;
641             }
642 0 0         unless ($res->is_success) {
643 0           my $code = $res->code;
644             # Not JSON result:
645 0 0         unless (defined($res->json)) {
646 0           my $text = $res->message;
647 0           print STDERR "ERROR: Alert API call failed: $code, $text\n";
648 0           return;
649             }
650 0           my $json = $res->json;
651             # If code 429, retry with some sleep:
652 0 0         if ($code == 429) {
653 0 0         print STDERR "WARNING: Too fast, sleeping before retry...\n" unless $self->silent;
654 0           sleep 1;
655 0           goto RETRY;
656             }
657             # Otherwise, stops and display error:
658 0 0         print STDERR "ERROR: Alert API call failed: $code, ".encode_json($json)."\n"
659             unless $self->silent;
660 0           return;
661             }
662              
663 0           my $json = $res->json;
664 0           $cb->($json, $cb_args);
665              
666 0           return 1;
667             }
668              
669             # $self->alert_list();
670 0     0 0   sub alert_list ($self, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
671 0           return $self->alert('get', '/alert/list', undef, undef, undef, undef, $cb, $cb_args);
672             }
673              
674             # $self->alert_add('test', 'category:datascan protocol:ssh', 'email@example.com', '>1000');
675 0     0 0   sub alert_add ($self, $name, $oql, $email, $threshold = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
676 0 0         if ($self->verbose) {
677 0           print STDERR "VERBOSE: name: [$name]\n";
678 0           print STDERR "VERBOSE: oql: [$oql]\n";
679 0           print STDERR "VERBOSE: email: [$email]\n";
680 0 0         print STDERR "VERBOSE: threshold: [$threshold]\n" if defined $threshold;
681             }
682 0           return $self->alert('post', '/alert/add', $name, $oql, $email, $threshold, $cb, $cb_args);
683             }
684              
685             # $self->alert_del(0);
686 0     0 0   sub alert_del ($self, $id, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
687 0           return $self->alert('post', '/alert/del/'.$id, undef, undef, undef, undef, $cb, $cb_args);
688             }
689              
690             #
691             # On-demand APIs:
692             #
693 0     0     sub _cb_ondemand ($self, $results = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
694 0     0     return sub ($results, $cb_args) {
  0            
  0            
  0            
695 0 0         $results = ref($results) eq 'ARRAY' ? $results : [ $results ];
696 0           for (@$results) {
697 0           print $self->encode($_)."\n";
698             }
699 0           };
700             }
701              
702 0     0 0   sub ondemand ($self, $method, $api, $param, $post, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
703 0           my $global = $self->config->{''};
704 0   0       my $endpoint = $global->{api_ondemand_endpoint} || $self->endpoint;
705 0   0       my $apikey = $global->{api_ondemand_key} || $self->apikey;
706              
707 0 0         die("FATAL: api_ondemand_key not configured in ~/.onyphe.ini\n") unless defined($apikey);
708 0 0         die("FATAL: api_ondemand_endpoint not configured in ~/.onyphe.ini\n") unless defined($endpoint);
709              
710             # Use default callback when none given:
711 0   0       $cb ||= $self->_cb_ondemand;
712              
713 0           my $ua = $self->_ua();
714 0           my $headers = $self->_headers($apikey);
715              
716 0           $api =~ s{^/*}{/}g;
717              
718 0           my $path = $endpoint.$api;
719 0           $path .= '?k='.$apikey;
720              
721 0 0         if (defined($param)) {
722 0 0         $post->{maxscantime} = $param->{maxscantime} if defined $param->{maxscantime};
723 0 0         $post->{aslines} = $param->{aslines} ? 'true' : 'false' if defined $param->{aslines};
    0          
724 0 0         $post->{aslink} = $param->{aslink} ? 'true' : 'false' if defined $param->{aslink};
    0          
725 0 0         $post->{full} = $param->{full} ? 'true' : 'false' if defined $param->{full};
    0          
726 0 0         $post->{urlscan} = $param->{urlscan} ? 'true' : 'false' if defined $param->{urlscan};
    0          
727 0 0         $post->{vulnscan} = $param->{vulnscan} ? 'true' : 'false' if defined $param->{vulnscan};
    0          
728 0 0         $post->{riskscan} = $param->{riskscan} ? 'true' : 'false' if defined $param->{riskscan};
    0          
729 0 0         $post->{asm} = $param->{asm} ? 'true' : 'false' if defined $param->{asm};
    0          
730 0 0         $post->{import} = $param->{import} ? 'true' : 'false' if defined $param->{import};
    0          
731 0 0         $post->{ports} = $param->{ports} if defined $param->{ports};
732             }
733              
734 0 0         print STDERR "VERBOSE: Calling API: $path\n" if $self->verbose;
735              
736 0           my $url = Mojo::URL->new($path);
737              
738 0           my @args = ( $url => $headers );
739 0 0         @args = ( $url => $headers => json => $post ) if defined $post;
740              
741             #print STDERR "DEBUG: args: ".Data::Dumper::Dumper(\@args)."\n";
742              
743 0           RETRY:
744             my $res;
745 0           eval {
746 0           $res = $ua->$method(@args)->result;
747             };
748 0 0         if ($@) {
749 0           chomp($@);
750 0 0         print STDERR "WARNING: Ondemand API call failed: [$@], retrying...\n" unless $self->silent;
751 0           goto RETRY;
752             }
753 0 0         unless ($res->is_success) {
754 0           my $code = $res->code;
755             # Not JSON result:
756 0 0         unless (defined($res->json)) {
757 0           my $text = $res->message;
758 0           print STDERR "ERROR: Request API call failed: $code, $text\n";
759 0           return;
760             }
761             #print Data::Dumper::Dumper($res->body)."\n";
762 0           my $json = $res->json;
763             # If code 429, retry with some sleep:
764 0 0         if ($code == 429) {
765 0 0         print STDERR "WARNING: Too fast, sleeping before retry...\n" unless $self->silent;
766 0           sleep 1;
767 0           goto RETRY;
768             }
769             # Otherwise, stops and display error:
770 0 0         print STDERR "ERROR: Ondemand API call failed: $code, ".encode_json($json)."\n"
771             unless $self->silent;
772 0           return;
773             }
774              
775 0           my $data;
776 0 0 0       if (defined($param) && $param->{aslines}) {
777 0           my @lines = split(/\r?\n/, $res->body);
778 0           $data = \@lines;
779             }
780             else {
781 0           $data = $res->json;
782             }
783 0           $cb->($data, $cb_args);
784              
785 0           return 1;
786             }
787              
788 0     0 0   sub ondemand_scope_ip ($self, $target, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
789 0           return $self->ondemand('post', '/ondemand/scope/ip/single', $param, { ip => $target }, $cb, $cb_args);
790             }
791              
792 0     0 0   sub ondemand_scope_port ($self, $target, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
793 0           return $self->ondemand('post', '/ondemand/scope/port/single', $param, { port => $target }, $cb, $cb_args);
794             }
795              
796 0     0 0   sub ondemand_scope_domain ($self, $target, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
797 0           return $self->ondemand('post', '/ondemand/scope/domain/single', $param, { domain => $target }, $cb, $cb_args);
798             }
799              
800 0     0 0   sub ondemand_scope_hostname ($self, $target, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
801 0           return $self->ondemand('post', '/ondemand/scope/hostname/single', $param, { hostname => $target }, $cb, $cb_args);
802             }
803              
804 0     0 0   sub ondemand_scope_ip_bulk ($self, $file, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
805 0 0         if (! -f $file) {
806 0 0         print STDERR "ERROR: Ondemand Scope Ip Bulk needs a file as input\n"
807             unless $self->silent;
808             }
809              
810 0           my @lines = read_file($file);
811 0           for (@lines) { chomp };
  0            
812 0 0         unless (@lines) {
813 0 0         print STDERR "ERROR: Ondemand Scope Ip Bulk needs a file with content\n"
814             unless $self->silent;
815             }
816              
817 0           my $target = join(',', @lines);
818              
819 0           return $self->ondemand('post', '/ondemand/scope/ip/bulk', $param, { ip => $target }, $cb, $cb_args);
820             }
821              
822 0     0 0   sub ondemand_scope_domain_bulk ($self, $file, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
823 0 0         if (! -f $file) {
824 0 0         print STDERR "ERROR: Ondemand Scope Domain Bulk needs a file as input\n"
825             unless $self->silent;
826             }
827              
828 0           my @lines = read_file($file);
829 0           for (@lines) { chomp };
  0            
830 0 0         unless (@lines) {
831 0 0         print STDERR "ERROR: Ondemand Scope Domain Bulk needs a file with content\n"
832             unless $self->silent;
833             }
834              
835 0           my $target = join(',', @lines);
836              
837 0           return $self->ondemand('post', '/ondemand/scope/domain/bulk', $param, { domain => $target }, $cb, $cb_args);
838             }
839              
840 0     0 0   sub ondemand_scope_hostname_bulk ($self, $file, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
841 0 0         if (! -f $file) {
842 0 0         print STDERR "ERROR: Ondemand Scope Hostname Bulk needs a file as input\n"
843             unless $self->silent;
844             }
845              
846 0           my @lines = read_file($file);
847 0           for (@lines) { chomp };
  0            
848 0 0         unless (@lines) {
849 0 0         print STDERR "ERROR: Ondemand Scope Hostname Bulk needs a file with content\n"
850             unless $self->silent;
851             }
852              
853 0           my $target = join(',', @lines);
854              
855 0           return $self->ondemand('post', '/ondemand/scope/hostname/bulk', $param, { hostname => $target }, $cb, $cb_args);
856             }
857              
858 0     0 0   sub ondemand_scope_result ($self, $scan_id, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
859 0           return $self->ondemand('get', '/ondemand/scope/result/'.$scan_id, $param, undef, $cb, $cb_args);
860             }
861              
862 0     0 0   sub ondemand_resolver_ip ($self, $target, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
863 0           return $self->ondemand('post', '/ondemand/resolver/ip/single', $param, { ip => $target }, $cb, $cb_args);
864             }
865              
866 0     0 0   sub ondemand_resolver_domain ($self, $target, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
867 0           return $self->ondemand('post', '/ondemand/resolver/domain/single', $param, { domain => $target }, $cb, $cb_args);
868             }
869              
870 0     0 0   sub ondemand_resolver_domain_bulk ($self, $file, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
871 0 0         if (! -f $file) {
872 0 0         print STDERR "ERROR: Ondemand Resolver Domain Bulk needs a file as input\n"
873             unless $self->silent;
874             }
875              
876 0           my @lines = read_file($file);
877 0           for (@lines) { chomp };
  0            
878 0 0         unless (@lines) {
879 0 0         print STDERR "ERROR: Ondemand Resolver Domain Bulk needs a file with content\n"
880             unless $self->silent;
881             }
882              
883 0           my $target = join(',', @lines);
884              
885 0           return $self->ondemand('post', '/ondemand/resolver/domain/bulk', $param, { domain => $target }, $cb, $cb_args);
886             }
887              
888 0     0 0   sub ondemand_resolver_hostname ($self, $target, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
889 0           return $self->ondemand('post', '/ondemand/resolver/hostname/single', $param, { hostname => $target }, $cb, $cb_args);
890             }
891              
892 0     0 0   sub ondemand_resolver_result ($self, $scan_id, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
893 0           return $self->ondemand('get', '/ondemand/resolver/result/'.$scan_id, $param, undef, $cb, $cb_args);
894             }
895              
896             #
897             # ASD APIs:
898             #
899 0     0     sub _cb_asd ($self, $results = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
900 0     0     return sub ($results, $cb_args) {
  0            
  0            
  0            
901 0 0         $results = ref($results) eq 'ARRAY' ? $results : [ $results ];
902 0           for (@$results) {
903 0           print $self->encode($_)."\n";
904             }
905 0           };
906             }
907              
908 0     0 0   sub asd ($self, $method, $api, $param, $post, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
909 0           my $global = $self->config->{''};
910 0   0       my $endpoint = $global->{api_asd_endpoint} || $self->endpoint;
911 0   0       my $apikey = $global->{api_asd_key} || $self->apikey;
912              
913 0 0         die("FATAL: api_asd_key not configured in ~/.onyphe.ini\n") unless defined($apikey);
914 0 0         die("FATAL: api_asd_endpoint not configured in ~/.onyphe.ini\n") unless defined($endpoint);
915              
916             # Use default callback when none given:
917 0   0       $cb ||= $self->_cb_asd;
918              
919 0           my $ua = $self->_ua();
920 0           my $headers = $self->_headers($apikey);
921              
922 0           $api =~ s{^/*}{/}g;
923              
924 0           my $path = $endpoint.$api;
925 0           $path .= '?k='.$apikey;
926              
927 0 0         if (defined($param)) {
928 0 0         $post->{domain} = $param->{domain} if defined $param->{domain};
929 0 0         $post->{aslines} = $param->{aslines} ? 'true' : 'false' if defined $param->{aslines};
    0          
930 0 0         $post->{astask} = $param->{astask} ? 'true' : 'false' if defined $param->{astask};
    0          
931 0 0         $post->{trusted} = $param->{trusted} ? 'true' : 'false' if defined $param->{trusted};
    0          
932 0 0         $post->{field} = $param->{field} if defined $param->{field};
933 0 0         $post->{query} = $param->{query} if defined $param->{query};
934             $post->{includep} = $self->_load_file($param->{includep})
935 0 0         if defined($param->{includep});
936             $post->{excludep} = $self->_load_file($param->{excludep})
937 0 0         if defined($param->{excludep});
938             }
939              
940 0 0         print STDERR "VERBOSE: Calling API: $path\n" if $self->verbose;
941 0 0         print STDERR "VERBOSE: Calling API with content: ".Data::Dumper::Dumper($post)."\n"
942             if $self->verbose;
943              
944 0           my $url = Mojo::URL->new($path);
945              
946 0           my @args = ( $url => $headers );
947 0 0         @args = ( $url => $headers => json => $post ) if defined $post;
948              
949             #print STDERR "DEBUG: args: ".Data::Dumper::Dumper(\@args)."\n";
950              
951 0           RETRY:
952             my $res;
953 0           eval {
954 0           $res = $ua->$method(@args)->result;
955             };
956 0 0         if ($@) {
957 0           chomp($@);
958 0 0         if ($@ =~ m{Premature connection close}i) {
959 0           return 1;
960             }
961 0 0         print STDERR "WARNING: ASD API call failed: [$@], retrying...\n" unless $self->silent;
962 0           goto RETRY;
963             }
964 0 0         unless ($res->is_success) {
965 0           my $code = $res->code;
966             # Not JSON result:
967 0 0         unless (defined($res->json)) {
968 0           my $text = $res->message;
969 0           print STDERR "ERROR: ASD API call failed: $code, $text\n";
970 0           return;
971             }
972             #print Data::Dumper::Dumper($res->body)."\n";
973 0           my $json = $res->json;
974             # If code 429, retry with some sleep:
975 0 0         if ($code == 429) {
976 0 0         print STDERR "WARNING: Too fast, sleeping before retry...\n" unless $self->silent;
977 0           sleep 1;
978 0           goto RETRY;
979             }
980             # Otherwise, stops and display error:
981 0 0         print STDERR "ERROR: ASD API call failed: $code, ".encode_json($json)."\n"
982             unless $self->silent;
983 0           return;
984             }
985              
986 0           my $data;
987 0 0 0       if (defined($param) && $param->{aslines}) {
988 0           my @lines = split(/\r?\n/, $res->body);
989 0           $data = \@lines;
990             }
991             else {
992 0           $data = $res->json;
993             }
994 0           $cb->($data, $cb_args);
995              
996 0           return 1;
997             }
998              
999 0     0     sub _load_file ($self, $arg) {
  0            
  0            
  0            
1000 0 0         if (-f $arg) { # If its a file, we create the list of values to push
1001 0           my $docs = $self->asd_load_input($arg);
1002 0 0 0       unless (defined($docs) && @$docs) {
1003 0           print STDERR "VERBOSE: asd_load_input: failed from bad content or empty content\n";
1004 0           return;
1005             }
1006 0           $arg = $docs;
1007             }
1008             else {
1009 0           $arg = [ split(',', $arg) ];
1010             }
1011              
1012 0           return $arg;
1013             }
1014              
1015 0     0     sub _arg_from_field ($self, $arg, $field) {
  0            
  0            
  0            
  0            
1016 0 0 0       return $arg if ref($arg) eq 'ARRAY' && !ref($arg->[0]);
1017 0           my $values = [];
1018 0           for (@$arg) {
1019 0 0         next unless defined($_->{$field});
1020 0           push @$values, $_->{$field};
1021             }
1022 0           return $values;
1023             }
1024              
1025 0     0 0   sub asd_pivot_query ($self, $arg, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1026 0           return $self->asd('post', '/asd/pivot/query', $param, { query => $arg }, $cb, $cb_args);
1027             }
1028              
1029 0     0 0   sub asd_domain_tld ($self, $arg, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1030 0           $arg = $self->_load_file($arg);
1031 0           $arg = $self->_arg_from_field($arg, 'domain');
1032 0           return $self->asd('post', '/asd/domain/tld', $param, { domain => $arg }, $cb, $cb_args);
1033             }
1034              
1035 0     0 0   sub asd_dns_domain_ns ($self, $arg, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1036 0           $arg = $self->_load_file($arg);
1037 0           $arg = $self->_arg_from_field($arg, 'domain');
1038 0           return $self->asd('post', '/asd/dns/domain/ns', $param, { domain => $arg }, $cb, $cb_args);
1039             }
1040              
1041 0     0 0   sub asd_dns_domain_mx ($self, $arg, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1042 0           $arg = $self->_load_file($arg);
1043 0           $arg = $self->_arg_from_field($arg, 'domain');
1044 0           return $self->asd('post', '/asd/dns/domain/mx', $param, { domain => $arg }, $cb, $cb_args);
1045             }
1046              
1047 0     0 0   sub asd_certso_domain ($self, $arg, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1048 0           $arg = $self->_load_file($arg);
1049 0           $arg = $self->_arg_from_field($arg, 'domain');
1050 0           return $self->asd('post', '/asd/certso/domain', $param, { domain => $arg }, $cb, $cb_args);
1051             }
1052              
1053 0     0 0   sub asd_certso_wildcard ($self, $arg, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1054 0           $arg = $self->_load_file($arg);
1055 0           $arg = $self->_arg_from_field($arg, 'subject.organization');
1056 0           return $self->asd('post', '/asd/certso/wildcard', $param, { certso => $arg }, $cb, $cb_args);
1057             }
1058              
1059 0     0     sub _arg_from_inventory ($self, $arg, $default) {
  0            
  0            
  0            
  0            
1060 0 0 0       return { $default => $arg } if ref($arg) eq 'ARRAY' && !ref($arg->[0]);
1061 0           my $values = {};
1062 0           for my $doc (@$arg) {
1063 0           for my $field (keys %$doc) {
1064 0 0         next unless defined($doc->{$field});
1065 0           push @{$values->{$field}}, $doc->{$field};
  0            
1066             }
1067             }
1068 0           return $values;
1069             }
1070              
1071 0     0 0   sub asd_org_inventory ($self, $arg, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1072 0           $arg = $self->_load_file($arg);
1073 0           $arg = $self->_arg_from_inventory($arg, 'domain');
1074 0           return $self->asd('post', '/asd/org/inventory', $param, { inventory => $arg }, $cb, $cb_args);
1075             }
1076              
1077 0     0 0   sub asd_ip_inventory ($self, $arg, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1078 0           $arg = $self->_load_file($arg);
1079 0           $arg = $self->_arg_from_inventory($arg, 'domain');
1080 0           return $self->asd('post', '/asd/ip/inventory', $param, { inventory => $arg }, $cb, $cb_args);
1081             }
1082              
1083 0     0 0   sub asd_subnet_inventory ($self, $arg, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1084 0           $arg = $self->_load_file($arg);
1085 0           $arg = $self->_arg_from_inventory($arg, 'domain');
1086 0           return $self->asd('post', '/asd/subnet/inventory', $param, { inventory => $arg }, $cb, $cb_args);
1087             }
1088              
1089 0     0 0   sub asd_vhost_inventory ($self, $arg, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1090 0           $arg = $self->_load_file($arg);
1091 0           $arg = $self->_arg_from_inventory($arg, 'domain');
1092 0           return $self->asd('post', '/asd/vhost/inventory', $param, { inventory => $arg }, $cb, $cb_args);
1093             }
1094              
1095 0     0 0   sub asd_score_inventory ($self, $arg, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1096 0           $arg = $self->_load_file($arg);
1097 0           $arg = $self->_arg_from_inventory($arg, 'domain');
1098 0           return $self->asd('post', '/asd/score/inventory', $param, { inventory => $arg }, $cb, $cb_args);
1099             }
1100              
1101 0     0 0   sub asd_dns_domain_soa ($self, $arg, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1102 0           $arg = $self->_load_file($arg);
1103 0           $arg = $self->_arg_from_field($arg, 'domain');
1104 0           return $self->asd('post', '/asd/dns/domain/soa', $param, { domain => $arg }, $cb, $cb_args);
1105             }
1106              
1107 0     0 0   sub asd_domain_wildcard ($self, $arg, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1108 0           $arg = $self->_load_file($arg);
1109 0           $arg = $self->_arg_from_field($arg, 'domain');
1110 0           return $self->asd('post', '/asd/domain/wildcard', $param, { domain => $arg }, $cb, $cb_args);
1111             }
1112              
1113 0     0 0   sub asd_domain_certso ($self, $arg, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1114 0           $arg = $self->_load_file($arg);
1115 0           $arg = $self->_arg_from_field($arg, 'subject.organization');
1116 0           return $self->asd('post', '/asd/domain/certso', $param, { certso => $arg }, $cb, $cb_args);
1117             }
1118              
1119 0     0 0   sub asd_dns_domain_exist ($self, $arg, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1120 0           $arg = $self->_load_file($arg);
1121 0           $arg = $self->_arg_from_field($arg, 'domain');
1122 0           return $self->asd('post', '/asd/dns/domain/exist', $param, { domain => $arg }, $cb, $cb_args);
1123             }
1124              
1125 0     0 0   sub asd_task_id ($self, $taskid, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1126 0           return $self->asd('get', '/asd/task/id/'.$taskid, $param, undef, $cb, $cb_args);
1127             }
1128              
1129 0     0 0   sub asd_task_poll ($self, $taskid, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1130 0           return $self->asd('get', '/asd/task/poll/'.$taskid, $param, undef, $cb, $cb_args);
1131             }
1132              
1133 0     0 0   sub asd_task_list ($self, $taskid, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1134 0           return $self->asd('get', '/asd/task/list', $param, undef, $cb, $cb_args);
1135             }
1136              
1137 0     0 0   sub asd_task_kill ($self, $taskid, $param = undef, $cb = undef, $cb_args = undef) {
  0            
  0            
  0            
  0            
  0            
  0            
1138 0           return $self->asd('delete', '/asd/task/kill/'.$taskid, $param, undef, $cb, $cb_args);
1139             }
1140              
1141 0     0 0   sub asd_load_input ($self, $input) {
  0            
  0            
  0            
1142 0           my $docs = [];
1143 0           my @lines = read_file($input);
1144 0           for (@lines) {
1145 0           chomp;
1146 0 0         unless ($_ =~ m{[=:]}) { # Need a key:value pair
1147 0           print STDERR "ERROR: asd_loas_input: invalid line found[$_], skipping\n";
1148 0           next;
1149             }
1150 0           s{(?:^\s*|\s*)$}{}g;
1151             #utf8::encode($line); # Not required, already in UTF-8
1152 0           my ($k, $v) = split(/\s*[=:]\s*/, $_, 2);
1153 0 0 0       next unless (defined($k) && defined($v));
1154 0           $v =~ s{(?:^["']|["']$)}{}g;
1155 0           push @$docs, { $k => $v };
1156             }
1157              
1158 0 0         print STDERR "VERBOSE: loaded ASD file: $input: ".Data::Dumper::Dumper($docs)."\n"
1159             if $self->verbose;
1160              
1161 0           return $docs;
1162             }
1163              
1164             1;
1165              
1166             __END__