0d4e1fa51d72fc9aa4e7b81d07afa3af0075a83e
[sks-keyservers-pool.git] / sks-keyservers.net / status-srv / sks_get_peer_data.php
1 <?php
2  /*
3   *  status-srv/sks_get_peer_data.php
4   *  Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012  Kristian Fiskerstrand
5   *  
6   *  This file is part of SKS Keyserver Pool (http://sks-keyservers.net)
7   *  
8   *  The Author can be reached by electronic mail at kf@sumptuouscapital.com
9   *  Communication using OpenPGP is preferred - a copy of the public key 0x0B7F8B60E3EDFAE3
10   *  is available in all the common keyservers or in hkp://pool.sks-keyservers.net
11   *  
12   *  This program is free software: you can redistribute it and/or modify
13   *  it under the terms of the GNU General Public License as published by
14   *  the Free Software Foundation, either version 3 of the License, or
15   *  (at your option) any later version.
16   *
17   *  This program is distributed in the hope that it will be useful,
18   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20   *  GNU General Public License for more details.
21   *
22   *  You should have received a copy of the GNU General Public License
23   *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
24   */
25
26   /*
27    * Define debug levels for output data based on SKS_DEBUG_LEVEL env variable
28    * 0    Normal (no debug info)
29    * 1-3  Reserved
30    * 4    Report TLS/SSL certificate errors
31    * 5    All available debug information
32    */
33
34   $debug = 0;
35   if(getenv("SKS_DEBUG_LEVEL"))
36         $debug = (int)getenv("SKS_DEBUG_LEVEL");
37  
38   if(!isset($argv[1]))
39   {
40         if($debug >= 1)
41                 echo __LINE__.": No argument provided, exiting\n";
42         exit;
43   }
44
45   $host = $argv[1]; //Set host to first argument
46   $port=11371; //Manually set port
47  
48   $require_CA = true; 
49     
50   include_once("/webs/sks-keyservers.net/status/exclude.inc.php");
51   require_once('Net/DNS2.php');
52     
53   if(ServerIsExcluded($host)) 
54         exit; 
55   
56   /*
57    * Force sleep at random interval to spread load
58    */ 
59    
60   sleep(rand(1,15));
61   
62   function gethostbyname6($host, $try_a = false) {
63         // get AAAA record for $host
64         // if $try_a is true, if AAAA fails, it tries for A
65         // the first match found is returned
66         // otherwise returns false
67
68         $dns = gethostbynamel6($host, $try_a);
69         if ($dns == false) { return false; }
70         else { return $dns[0]; }
71     }
72
73     function gethostbynamel6($host, $try_a = false) {
74         // get AAAA records for $host,
75         // if $try_a is true, if AAAA fails, it tries for A
76         // results are returned in an array of ips found matching type
77         // otherwise returns false
78
79         $dns6 = dns_get_record($host, DNS_AAAA);
80         if ($try_a == true) {
81             $dns4 = dns_get_record($host, DNS_A);
82             $dns = array_merge($dns4, $dns6);
83         }
84         else { $dns = $dns6; }
85         $ip6 = array();
86         $ip4 = array();
87         foreach ($dns as $record) {
88             if ($record["type"] == "A") {
89                 $ip4[] = $record["ip"];
90             }
91             if ($record["type"] == "AAAA") {
92                 $ip6[] = $record["ipv6"];
93             }
94         }
95         if (count($ip6) < 1) {
96             if ($try_a == true) {
97                 if (count($ip4) < 1) {
98                     return false;
99                 }
100                 else {
101                     return $ip4;
102                 }
103             }
104             else {
105                 return false;
106             }
107         }
108         else {
109             return $ip6;
110         }
111     }
112   
113
114   function microtime_float()
115   {
116     list($usec, $sec) = explode(" ", microtime());
117     return ((float)$usec + (float)$sec);
118   }
119
120   function get_tor_addresse($server)
121   {
122       $tor_addresses = array(
123           'keys2.kfwebs.net' => 'dyh2j3qyrirn43iw.onion',
124           'zimmermann.mayfirst.org' => 'qdigse2yzvuglcix.onion',
125           'pgpkeys.urown.net' => 'pgpkeysximvxiazm.onion',
126           'sks.srv.dumain.com' => 'obrrsrw6b3rjuibx.onion',
127           'keys.void.gr' => 'wooprzddebtxfhnq.onion',
128           'ams.sks.heypete.com' => 'nfkrkvghv75xsf26.onion',
129           'pgp.ohai.su' => 'pgp7fqno3yks7mc4.onion',
130           'keyserver.c3l.lu' => 'xogxzfyhwmgfvmlr.onion',
131           'keyserver.adamas.ai' => 'tviniih5jbm3j23b.onion',
132           'sks.daylightpirates.org' => 'ai3dvhjytrgice5h.onion',
133           'sks.fidocon.de' => 'nhzgrlwhukwtajz4.onion',
134           'keyserver.siccegge.de' => '47hbff4rtpwfpwlr.onion',
135           'keys.bonus-communis.eu' => '37hyu2hzynpjwuaw.onion',
136           'keys.jhcloos.com' => 'bcxmzluy3snubdn4.onion',
137           'vanunu.calyxinstitute.org' => 'tsc64wi45alh6rkq.onion',
138           'keys.nerds.lu' => 'o4wwol2kcqzwszjz.onion',
139           'pgp.key-server.io' => 'gnjtzu5c2lv4zasv.onion',
140           'gpg.nebrwesleyan.edu' => 'rphieza64zhfj2gk.onion',
141           'keys.andreas-puls.de' => 'eyi2mbamj4aeqwul.onion',
142           'sks.bonus-communis.eu' => 'kbbqa63mo7cchzut.onion',
143           'keyserver.ntzwrk.org' => 'keys2zvsn7kj7ly3.onion',
144           'keys.fspproductions.biz' => '7fymt4bkw6z7esbs.onion',
145           'a.keys.wolfined.com' => 'gpgkeyqrh46teug7.onion'
146       );
147
148       if(isset($tor_addresses[strtolower($server)]))
149         return $tor_addresses[strtolower($server)];
150       else
151         return 0;
152   }
153
154   function sanitize_hostname($host)
155   {
156         $ret = strtolower($host);
157         $ret = preg_replace("/[^a-z0-9\.\-]/", "", $ret);
158         return $ret;
159   }
160
161   function is_private_ip($host)
162   {
163     // Is this hostname actually fully IP addresse?
164     if(!preg_match("/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/", $host))
165         return false;
166
167     //RFC1918, link-local and local ranges
168     $private_ranges = array(
169         array("10.0.0.0", "10.255.255.255"),
170         array("172.16.0.0", "172.31.255.255"),
171         array("192.168.0.0", "192.168.255.255"),
172         array("169.254.0.0", "169.254.255.255"),
173         array("127.0.0.1", "127.255.255.255")
174     );
175
176     $ip = ip2long($host);
177     if($ip === false)
178         return false;
179
180     foreach($private_ranges as $range)
181     {
182         if($ip >= ip2long($range[0]) && $ip <= ip2long($range[1]))
183             return true;
184     }
185
186     return false;
187   }
188
189   function skip_peer($host)
190   {
191      $ret = false;
192
193      if(ServerIsExcluded($host))
194           $ret = true;
195
196      if(is_private_ip($host))
197           $ret = true;
198
199      return $ret;
200   }
201    
202    $return_array['hostname'] = $host;
203    $return_array['called_hostname'] = $host;
204    
205    $return_array['port'] = $port;
206    
207    $return_array['statusok'] = true; //Default status OK to true
208    
209    $timestart = microtime_float(); 
210    if($debug >= 5)
211     echo __LINE__.": Host: ${host}\n";
212     
213    $ch = curl_init("http://$host:$port/pks/lookup?op=stats&options=mr");
214    
215    /*
216     * Force the use of HTTP Host header. In the event a virtual machine
217     * setup is used, we want to only include servers configured 
218     * to accept the pool hosts. 
219     */
220    $http_headers = array("Host: pool.sks-keyservers.net");
221    
222    curl_setopt($ch, CURLOPT_HTTPHEADER, $http_headers);
223    curl_setopt($ch, CURLOPT_HEADER, 1); 
224    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
225    /*
226     *   Force IPv4 check here, IPv6 is checked for later
227     *   IPv6 only servers will not be included in the pool as long as this remain in place.
228     *   Require PHP 5.3 or later - see https://bugs.php.net/bug.php?id=47739  
229     */
230    curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);  
231    curl_setopt($ch, CURLOPT_TIMEOUT, 15);
232    if(($ret=curl_exec($ch))===FALSE || curl_getinfo($ch, CURLINFO_HTTP_CODE) != "200")
233    {
234     curl_close($ch);
235     $return_array['statusok']=false;
236    }
237    else
238    {   
239         if($debug >= 5)
240                 echo curl_error($ch);
241      
242     curl_close($ch);
243     $return_array['responsetime'] = microtime_float() - $timestart; 
244     
245     // Check server version in HTTP response
246     
247     $return_array['http_response_server'] = false; 
248     $return_array['http_response_via'] = false; 
249     
250     preg_match("/Server:\s+(.+)/", $ret, $matches);
251     if(isset($matches[1]))
252         $return_array['http_response_server'] = trim($matches[1]); 
253     unset($matches);
254         
255         preg_match("/Via:\s+(.+)/", $ret, $matches);
256     if(isset($matches[1]))
257         $return_array['http_response_via'] = true;
258     unset($matches);
259
260
261     // We are done checking server headers. Chomp it off
262     $header_split = strpos($ret, "\r\n\r\n");  
263     $ret = substr($ret, $header_split); 
264
265     // Set Software default to SKS, newer forks should have explicitly software names set
266     $return_array['software'] = "SKS";
267
268     // Check for json response and prosess that
269     $json_server_data = json_decode($ret, true); 
270     if($json_server_data !== NULL) 
271     {
272         if($debug >= 5) 
273         {
274                 echo "JSON prosessed OK"; 
275                 print_r($json_server_data); 
276         }
277
278         // workaround check for hockeypuck bug 
279         // https://bugs.launchpad.net/hockeypuck/+bug/1313096
280         if($json_server_data['hostname'] != "pool.sks-keyservers.net") 
281                 $return_array['hostname'] = $json_server_data['hostname'];
282
283         @$return_array['server_contact'] = $json_server_data['server_contact'];
284         @$return_array['software'] = $json_server_data['software']; 
285         @$return_array['version'] = $json_server_data['version']; 
286         @$return_array['numkeys'] = $json_server_data['numkeys']; 
287         // $return_array['recon_port']
288         // $return_array['peers'][]
289
290     }
291     else    // No json response found in request, process using old-style HTML parsing
292     {
293             // Set hostname based on server information
294             preg_match("#<tr><td>Hostname:(?:</td>)?<td>([^<]+)(?:</td></tr>)?#", $ret, $matches);
295             if(isset($matches[1]))
296                 $return_array['hostname'] = trim($matches[1]); 
297             unset($matches);
298             
299             
300             // Set server contact server information
301             $return_array['server_contact'] = "";
302             preg_match("#<tr><td>Server contact:(?:</td>)?<td>([^<]+)(?:</td></tr>)?#", $ret, $matches);
303             if(isset($matches[1]))
304                 $return_array['server_contact'] = trim($matches[1]); 
305             unset($matches);
306              
307             //Set recon port based on server information
308             $return_array['recon_port'] = "(null)";
309             preg_match("#<tr><td>Recon port:(?:</td>)?<td>([^<]+)(?:</td></tr>)?#", $ret, $matches);
310             if(isset($matches[1]))
311                 $return_array['recon_port'] = trim($matches[1]); 
312             unset($matches);
313             
314             // Set number of keys
315             preg_match("/Total number of keys:\s+(\d+)/",$ret,$matches);
316             if(isset($matches[1])) 
317                 $return_array['numkeys'] = $matches[1];
318             else
319                 $return_array['numkeys'] = 0;
320                 
321             unset($matches);
322                     
323             $matches = null; 
324             preg_match("#<tr><td>Software:(?:</td>)?<td>([^<]+)(?:</td></tr>)?#", $ret, $matches);
325             if(isset($matches[1])) 
326                 $return_array['software'] = $matches[1];        
327             unset($matches);
328             
329             // Set version
330             preg_match("/Version:.+?([\d\.\+]+)/",$ret,$matches);
331             if(isset($matches[1])) 
332                 $return_array['version'] = $matches[1];
333             else
334                 $return_array['version'] = 0;
335                 
336             unset($matches);
337             
338             // populate peers
339             $ret = strtr($ret,array("\n"=>""));
340             preg_match("/<h2>Gossip Peers<\/h2><table[^>]*>(.*?)<\/table>/",$ret,$matches);
341             if(isset($matches[1]))
342             {
343                 preg_match_all("/<tr><td>([a-zA-Z0-9\.\-]+)\s+(\d+)/",$matches[1],$matches2);
344             
345                     foreach($matches2[1] as $id=>$hosts)
346                     {
347                         if(skip_peer($hosts))
348                             continue;
349
350                      $return_array['peers'][] = sanitize_hostname($hosts);
351                     }
352             }
353             
354            
355             unset($matches); 
356             unset($matches2);
357             
358    }
359
360    // Set host to detected hostname rather than passed peer data
361    $host = $return_array['hostname'];
362
363    $return_array['tor_addresse'] = get_tor_addresse($host);
364    
365    // SETUP ipv6
366    $blacklistv6 = array();
367    // Check IPv6 status
368    $return_array['statusipv6ok'] = false;
369     
370    $ipv6_addy = gethostbyname6($host);
371    if(!is_array($ipv6_addy) && $ipv6_addy !== false && !in_array($host, $blacklistv6))
372    {
373         $ipv6_uri = "http://s01.sks-keyservers.net/sks-client/ipv6-test.php?keyserver=$host&ipv6=".urlencode($ipv6_addy)."";
374
375         if($debug >= 5)
376                 echo "Sending request to $ipv6_uri";
377
378         $ch_ipv6 = curl_init($ipv6_uri);
379         curl_setopt($ch_ipv6, CURLOPT_RETURNTRANSFER, true);
380         if(($ret=curl_exec($ch_ipv6))!==FALSE && curl_getinfo($ch_ipv6, CURLINFO_HTTP_CODE) == "200"){
381                 curl_close($ch_ipv6);
382                 $ipv6_json = json_decode($ret, true);
383
384                 if($debug >= 5)
385                         var_dump($ipv6_json);
386
387                 $return_array['statusipv6ok'] = $ipv6_json['statusipv6ok'];
388         } else {
389                 if($debug >= 5)
390                         echo "!!! IPv6 check failed";
391         }
392    }
393    /* 
394     * Check POST data with Expect header. (Issue 12)
395     */
396    
397    $return_array['postExpect'] = false;
398    
399    $http_headers = array("Host: pool.sks-keyservers.net", "Expect: 100-continue");
400    $chPE = curl_init("http://$host:$port/pks/add");
401    curl_setopt($chPE, CURLOPT_HTTPHEADER, $http_headers);
402    curl_setopt($chPE, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
403    $key = <<<EOF
404 -----BEGIN PGP PUBLIC KEY BLOCK-----
405 -----END PGP PUBLIC KEY BLOCK-----
406 EOF;
407    
408    $post_fields = array("keytext" => urlencode($key));
409    $post_fields_string = "";
410    foreach($post_fields as $key=>$value) 
411    { 
412     $post_fields_string .= $key.'='.$value.'&'; 
413    }
414    curl_setopt($chPE, CURLOPT_POST, count($post_fields));
415    curl_setopt($chPE, CURLOPT_POSTFIELDS, $post_fields_string);
416    curl_setopt($chPE, CURLOPT_RETURNTRANSFER, 1);
417    if($ret = curl_exec($chPE) !== FALSE)
418    {
419         if(curl_getinfo($chPE, CURLINFO_HTTP_CODE) == 417)
420           $return_array['postExpect'] = true;  
421         curl_close($chPE); 
422    }
423    /*
424     * Check for port 80
425     */
426    
427    $return_array['port80'] = false;
428    
429    $http_headers = array("Host: p80.pool.sks-keyservers.net");
430    
431    $ch80 = curl_init("http://$host:80/pks/lookup?op=stats");
432    curl_setopt($ch80, CURLOPT_HTTPHEADER, $http_headers);
433    curl_setopt($ch80, CURLOPT_HEADER, 1); 
434    curl_setopt($ch80, CURLOPT_RETURNTRANSFER, 1);
435    /*
436     *   Force IPv4 check here, IPv6 is checked for later
437     *   IPv6 only servers will not be included in the pool as long as this remain in place.
438     *   Require PHP 5.3 or later - see https://bugs.php.net/bug.php?id=47739  
439     */
440    curl_setopt($ch80, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);  
441    curl_setopt($ch80, CURLOPT_TIMEOUT, 15);
442    if(($ret=curl_exec($ch80))===FALSE)
443    {
444     curl_close($ch80);
445     $return_array['port80']=false;
446    }
447    else
448    {
449         if(curl_getinfo($ch80, CURLINFO_HTTP_CODE) == "200" && curl_getinfo($ch80, CURLINFO_SIZE_DOWNLOAD) > 2000)
450                    $return_array['port80'] = true;
451     curl_close($ch80);
452    }
453    
454    /*
455     * Check for HTTPS/HKPS support. First a DNS lookup for a SRV record
456     * is performed. If none is found, try the default port of 443.  
457     */
458     
459     $return_array['has_hkps'] = false;
460     $return_array['hkps_port'] = false;  
461     
462     $resolver = new Net_DNS2_Resolver(array('nameservers' => array('127.0.0.1')));
463     $hkps_has_srv = true;
464     $hkps_port = 0; 
465      
466     try
467     {
468       $resolver_result = $resolver->query('_pgpkey-https._tcp.'.$return_array['hostname'], 'SRV');
469     } catch(Net_DNS2_Exception $e) 
470     {   
471         if($debug >= 5)
472           echo "::query() failed: ", $e->getMessage(), "\n";      
473         
474         $hkps_has_srv = false;
475     }
476     
477     if($hkps_has_srv)
478     {
479        foreach($resolver_result->answer as $SRVrr)
480        {
481           if($debug >= 5)
482               printf("%d: SRV Resolver: port=%d, host=%s\n", __LINE__, $SRVrr->port, $SRVrr->target);
483
484           if($SRVrr->type !== 'SRV') 
485           {
486             if($debug >= 5)
487                 echo __LINE__.": Record type not SRV ({$SRVrr}), bugging out\n";
488             continue;   
489           }
490              
491           
492           if($SRVrr->target != $return_array['hostname']) 
493                 {
494                         if($debug >= 5)
495                                 echo __LINE__.": Target not matching, bugging out ({$SRVrr->target} vs ${$return_array['hostname']})\n";
496                 
497                 continue;
498                 }
499                 
500           
501           if($SRVrr->port < 1 || $SRVrr->port > 65536)
502           {
503                   if($debug >= 5)
504                         echo "Port mismatch, bugging out\n";    
505           
506                   continue;
507           }
508              
509           
510           $hkps_port = $SRVrr->port;
511           if($debug >= 5)
512                 echo __LINE__.": HKPS port: {$hkps_port}\n";
513           break;
514        }
515     }
516     
517     if(!$hkps_has_srv || $hkps_port == 0)
518         $hkps_port = 443;
519
520     if($debug >= 5)
521         echo __LINE__.": HKPS port: {$hkps_port}\n";
522    
523    if($debug >= 5) 
524        echo __LINE__.": Host at stage 2 is {$host}\n";
525  
526    $curl_ip = gethostbyname($host);
527    if($curl_ip == $host)
528    {
529      $return_array['has_hkps']=false;
530    }
531    else
532    {
533            $http_headers = array("Host: hkps.pool.sks-keyservers.net");
534            
535            if($debug >= 5)
536            {
537                 echo __LINE__.": Attempting HKPS test on port {$hkps_port}\n";
538                 echo __LINE__.": IP: {$curl_ip}\n";
539            }
540
541            $chhkps = curl_init("https://hkps.pool.sks-keyservers.net:$hkps_port/pks/lookup?op=stats");
542            curl_setopt($chhkps, CURLOPT_HTTPHEADER, $http_headers);
543            curl_setopt($chhkps, CURLOPT_HEADER, 1); 
544            curl_setopt($chhkps, CURLOPT_RETURNTRANSFER, 1);
545            curl_setopt($chhkps, CURLOPT_CAINFO, '/webs/sks-keyservers.net/sks-keyservers.netCA.pem');
546            curl_setopt($chhkps, CURLOPT_CAPATH, '/dev/null');
547            // CURLOPT_CRLFILE is supported as of PHP 5.5.4 (patch for other 
548            // versions submitted at https://bugs.php.net/bug.php?id=65575 )
549            curl_setopt($chhkps, CURLOPT_CRLFILE, '/webs/sks-keyservers.net/ca/crl.pem'); 
550            /* CURLOPT_RESOLVE require PHP 5.5 or later */
551            curl_setopt($chhkps, CURLOPT_RESOLVE, array("hkps.pool.sks-keyservers.net:{$hkps_port}:{$curl_ip}"));
552            curl_setopt($chhkps, CURLOPT_SSL_VERIFYHOST, 2);
553            curl_setopt($chhkps, CURLOPT_SSL_VERIFYPEER, true);
554            // Restrict to TLS 1.2: CURL_SSLVERSION_TLSv1_2 (6). 
555            curl_setopt($chhkps, CURLOPT_SSLVERSION, 6);
556
557            
558            /*
559             *   Force IPv4 check here, IPv6 is checked for later
560             *   IPv6 only servers will not be included in the pool as long as this remain in place.
561             *   Require PHP 5.3 or later - see https://bugs.php.net/bug.php?id=47739  
562             */
563            curl_setopt($chhkps, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);  
564            curl_setopt($chhkps, CURLOPT_TIMEOUT, 15);
565            if(($ret=curl_exec($chhkps))===FALSE)
566            {
567             if($debug >= 5)
568                 echo "Curl error:".__LINE__.":".curl_error($chhkps)."\n";
569                  
570             curl_close($chhkps);
571             $return_array['has_hkps']=false;
572            }
573            else
574            {
575             if($debug >= 5)
576                 echo __LINE__.":".curl_error($chhkps)."\n";
577
578             if($debug >= 4)
579                 echo "SSL verification result: ".curl_getinfo($chhkps, CURLINFO_SSL_VERIFYRESULT)."\n";
580                       
581                 if(curl_getinfo($chhkps, CURLINFO_HTTP_CODE) == "200" && curl_getinfo($chhkps, CURLINFO_SIZE_DOWNLOAD) > 2000)
582                 {
583                 $return_array['has_hkps'] = true;
584                 $return_array['hkps_port'] = $hkps_port;
585                 }
586                         
587            curl_close($chhkps);
588            }
589         }
590
591         /*
592          * Check for CVE-2014-3207 
593          */     
594         $return_array['cve-2014-3207'] = true;
595         $runfile = dirname(__FILE__); 
596         $ret = `${runfile}/test_cve-2014-3207.sh $host`;
597
598         if(strpos($ret, "not affected") !== FALSE)
599                 $return_array['cve-2014-3207'] = false;
600    }    
601    // Return json encoded data
602    $json =  json_encode($return_array);
603    echo $json; 
604 ?>