/* * Created on Dec 20, 2004 * Created by Alon Rohter * Copyright (C) 2004-2005 Aelitis, All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * AELITIS, SAS au capital de 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package com.aelitis.azureus.core.versioncheck; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.net.*; import java.util.*; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.config.ParameterListener; import org.gudy.azureus2.core3.download.impl.DownloadManagerStateImpl; import org.gudy.azureus2.core3.internat.MessageText; import org.gudy.azureus2.core3.logging.*; import org.gudy.azureus2.core3.stats.transfer.*; import org.gudy.azureus2.core3.util.*; import org.gudy.azureus2.plugins.PluginInterface; import org.gudy.azureus2.plugins.utils.DelayedTask; import org.gudy.azureus2.pluginsimpl.local.utils.UtilitiesImpl; import com.aelitis.azureus.core.AzureusCore; import com.aelitis.azureus.core.AzureusCoreFactory; import com.aelitis.azureus.core.impl.AzureusCoreImpl; import com.aelitis.azureus.core.clientmessageservice.*; import com.aelitis.azureus.core.networkmanager.admin.NetworkAdmin; import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminASN; import com.aelitis.azureus.core.networkmanager.impl.udp.UDPNetworkManager; import com.aelitis.azureus.core.proxy.AEProxyFactory; import com.aelitis.azureus.core.proxy.AEProxyFactory.PluginProxy; import com.aelitis.azureus.core.security.CryptoManagerFactory; import com.aelitis.azureus.core.util.DNSUtils; import com.aelitis.net.udp.uc.PRUDPPacketHandler; import com.aelitis.net.udp.uc.PRUDPPacketHandlerFactory; import com.aelitis.net.udp.uc.PRUDPReleasablePacketHandler; /** * Client for checking version information from a remote server. */ public class VersionCheckClient { private static final LogIDs LOGID = LogIDs.CORE; public static final String REASON_UPDATE_CHECK_START = "us"; public static final String REASON_UPDATE_CHECK_PERIODIC = "up"; public static final String REASON_CHECK_SWT = "sw"; public static final String REASON_DHT_EXTENDED_ALLOWED = "dx"; public static final String REASON_DHT_ENABLE_ALLOWED = "de"; public static final String REASON_EXTERNAL_IP = "ip"; public static final String REASON_RECOMMENDED_PLUGINS = "rp"; public static final String REASON_SECONDARY_CHECK = "sc"; public static final String REASON_PLUGIN_UPDATE = "pu"; public static final String REASON_DHT_BOOTSTRAP = "db"; private static final String AZ_MSG_SERVER_ADDRESS_V4 = Constants.VERSION_SERVER_V4; private static final int AZ_MSG_SERVER_PORT = 27001; private static final String MESSAGE_TYPE_ID = "AZVER"; public static final String HTTP_SERVER_ADDRESS_V4 = AZ_MSG_SERVER_ADDRESS_V4; public static final int HTTP_SERVER_PORT = 80; public static final String TCP_SERVER_ADDRESS_V4 = AZ_MSG_SERVER_ADDRESS_V4; public static final int TCP_SERVER_PORT = 80; public static final String UDP_SERVER_ADDRESS_V4 = AZ_MSG_SERVER_ADDRESS_V4; public static final int UDP_SERVER_PORT = 2080; public static final String AZ_MSG_SERVER_ADDRESS_V6 = Constants.VERSION_SERVER_V6; public static final String HTTP_SERVER_ADDRESS_V6 = AZ_MSG_SERVER_ADDRESS_V6; public static final String TCP_SERVER_ADDRESS_V6 = AZ_MSG_SERVER_ADDRESS_V6; public static final String UDP_SERVER_ADDRESS_V6 = AZ_MSG_SERVER_ADDRESS_V6; private static final long CACHE_PERIOD = 5*60*1000; private static boolean secondary_check_done; private final List<VersionCheckClientListener> listeners = new ArrayList<VersionCheckClientListener>(1); private boolean startCheckRan = false; static{ VersionCheckClientUDPCodecs.registerCodecs(); } private static final int AT_V4 = 1; private static final int AT_V6 = 2; private static final int AT_EITHER = 3; private static VersionCheckClient instance; /** * Get the singleton instance of the version check client. * @return version check client */ public static synchronized VersionCheckClient getSingleton() { if ( instance == null ){ instance = new VersionCheckClient(); } return( instance ); } private boolean enable_v6; private boolean prefer_v6; private Map last_check_data_v4 = null; private Map last_check_data_v6 = null; private final AEMonitor check_mon = new AEMonitor( "versioncheckclient" ); private long last_check_time_v4 = 0; private long last_check_time_v6 = 0; private long last_feature_flag_cache; private long last_feature_flag_cache_time; private VersionCheckClient() { COConfigurationManager.addAndFireParameterListeners( new String[]{ "IPV6 Prefer Addresses", "IPV6 Enable Support" }, new ParameterListener() { public void parameterChanged( String name ) { enable_v6 = COConfigurationManager.getBooleanParameter( "IPV6 Enable Support" ); prefer_v6 = COConfigurationManager.getBooleanParameter( "IPV6 Prefer Addresses" ); } }); } public void initialise() { DelayedTask delayed_task = UtilitiesImpl.addDelayedTask( "VersionCheck", new Runnable() { public void run() { final AESemaphore sem = new AESemaphore( "VCC:init" ); new AEThread2( "VCC:init", true ) { public void run() { try{ getVersionCheckInfo( REASON_UPDATE_CHECK_START ); }finally{ sem.release(); } } }.start(); if ( !sem.reserve( 5000 )){ Debug.out( "Timeout waiting for version check to complete" ); } } }); delayed_task.queue(); } /** * Get the version check reply info. * @return reply data, possibly cached, if the server was already checked within the last minute */ public Map getVersionCheckInfo( String reason ) { return( getVersionCheckInfo( reason, AT_EITHER )); } public Map getVersionCheckInfo( String reason, int address_type ) { if ( address_type == AT_V4 ){ return( getVersionCheckInfoSupport( reason, false, false, false )); }else if ( address_type == AT_V6 ){ return( getVersionCheckInfoSupport( reason, false, false, true )); }else{ Map reply = getVersionCheckInfoSupport( reason, false, false, prefer_v6 ); if ( reply == null || reply.size() == 0 ){ reply = getVersionCheckInfoSupport( reason, false, false, !prefer_v6 ); } return( reply ); } } protected Map getVersionCheckInfoSupport( String reason, boolean only_if_cached, boolean force, boolean v6 ) { try { synchronized (listeners) { if (REASON_UPDATE_CHECK_START.equals(reason)) { startCheckRan = true; } for (VersionCheckClientListener l : listeners) { l.versionCheckStarted(reason); } } } catch (Throwable t) { Debug.out(t); } if ( v6 ){ if ( enable_v6 ){ try { check_mon.enter(); long time_diff = SystemTime.getCurrentTime() - last_check_time_v6; force = force || time_diff > CACHE_PERIOD || time_diff < 0; if( last_check_data_v6 == null || last_check_data_v6.size() == 0 || force ) { // if we've never checked before then we go ahead even if the "only_if_cached" // flag is set as its had not chance of being cached yet! if ( only_if_cached && last_check_data_v6 != null ){ return( new HashMap() ); } try { last_check_data_v6 = performVersionCheck( constructVersionCheckMessage( reason ), true, true, true ); if ( last_check_data_v6 != null && last_check_data_v6.size() > 0 ){ COConfigurationManager.setParameter( "versioncheck.cache.v6", last_check_data_v6 ); } } catch(SocketException t) { // internet is broken // Debug.out(t.getClass().getName() + ": " + t.getMessage()); } catch(UnknownHostException t) { // dns is broken // Debug.out(t.getClass().getName() + ": " + t.getMessage()); } catch( Throwable t ) { Debug.out(t); last_check_data_v6 = new HashMap(); } } else { Logger.log(new LogEvent(LOGID, "VersionCheckClient is using " + "cached version check info. Using " + last_check_data_v6.size() + " reply keys.")); } } finally { check_mon.exit(); } } if( last_check_data_v6 == null ) last_check_data_v6 = new HashMap(); return last_check_data_v6; }else{ try { check_mon.enter(); long time_diff = SystemTime.getCurrentTime() - last_check_time_v4; force = force || time_diff > CACHE_PERIOD || time_diff < 0; if( last_check_data_v4 == null || last_check_data_v4.size() == 0 || force ) { // if we've never checked before then we go ahead even if the "only_if_cached" // flag is set as its had not chance of being cached yet! if ( only_if_cached && last_check_data_v4 != null ){ return( new HashMap() ); } try { last_check_data_v4 = performVersionCheck( constructVersionCheckMessage( reason ), true, true, false ); if ( last_check_data_v4 != null && last_check_data_v4.size() > 0 ){ COConfigurationManager.setParameter( "versioncheck.cache.v4", last_check_data_v4 ); } // clear down any plugin-specific data that has successfully been sent to the version server try{ if ( AzureusCoreFactory.isCoreAvailable() && AzureusCoreFactory.getSingleton().getPluginManager().isInitialized()){ //installed plugin IDs PluginInterface[] plugins = AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaces(); for (int i=0;i<plugins.length;i++){ PluginInterface plugin = plugins[i]; Map data = plugin.getPluginconfig().getPluginMapParameter( "plugin.versionserver.data", null ); if ( data != null ){ plugin.getPluginconfig().setPluginMapParameter( "plugin.versionserver.data", new HashMap()); } } } }catch( Throwable e ){ } } catch( UnknownHostException t ) { // no internet Debug.outNoStack("VersionCheckClient - " + t.getClass().getName() + ": " + t.getMessage()); } catch (IOException t) { // General connection problem. Debug.outNoStack("VersionCheckClient - " + t.getClass().getName() + ": " + t.getMessage()); } catch( Throwable t ) { Debug.out(t); last_check_data_v4 = new HashMap(); } } else { if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "VersionCheckClient is using " + "cached version check info. Using " + last_check_data_v4.size() + " reply keys.")); } } finally { check_mon.exit(); } if( last_check_data_v4 == null ) last_check_data_v4 = new HashMap(); last_feature_flag_cache_time = 0; return last_check_data_v4; } } public Map getMostRecentVersionCheckData() { // currently we maintain v4 much more accurately than v6 if ( last_check_data_v4 != null ){ return( last_check_data_v4 ); } Map res = COConfigurationManager.getMapParameter( "versioncheck.cache.v4", null ); if ( res != null ){ return( res ); } if ( last_check_data_v6 != null ){ return( last_check_data_v6 ); } res = COConfigurationManager.getMapParameter( "versioncheck.cache.v6", null ); return( res ); } private boolean isVersionCheckDataValid( int address_type ) { boolean v6_ok = last_check_data_v6 != null && last_check_data_v6.size() > 0; boolean v4_ok = last_check_data_v4 != null && last_check_data_v4.size() > 0; if ( address_type == AT_V4 ){ return( v4_ok ); }else if ( address_type == AT_V6 ){ return( v6_ok ); }else{ return( v4_ok | v6_ok ); } } public long getCacheTime( boolean v6 ) { return( v6?last_check_time_v6:last_check_time_v4); } public void clearCache() { last_check_time_v6 = 0; last_check_time_v4 = 0; } public long getFeatureFlags() { long now = SystemTime.getCurrentTime(); if ( now > last_feature_flag_cache_time && now - last_feature_flag_cache_time < 60000 ){ return( last_feature_flag_cache ); } Map m = getMostRecentVersionCheckData(); long result; if ( m == null ){ result = 0; }else{ byte[] b_feat_flags = (byte[])m.get( "feat_flags" ); if ( b_feat_flags != null ){ try{ result = Long.parseLong(new String((byte[])b_feat_flags)); }catch( Throwable e ){ result = 0; } }else{ result = 0; } } last_feature_flag_cache = result; last_feature_flag_cache_time = now; return( result ); } public Set<String> getDisabledPluginIDs() { Set<String> result = new HashSet<String>(); Map m = getMostRecentVersionCheckData(); if ( m != null ){ byte[] x = (byte[])m.get( "disabled_pids" ); if ( x != null ){ String str = new String( x ); String latest = COConfigurationManager.getStringParameter( "vc.disabled_pids.latest", "" ); if ( !str.equals( latest )){ byte[] sig = (byte[])m.get( "disabled_pids_sig" ); if ( sig == null ){ Debug.out( "disabled plugins sig missing" ); return( result ); } try{ AEVerifier.verifyData( str, sig ); COConfigurationManager.setParameter( "vc.disabled_pids.latest", str ); }catch( Throwable e ){ return( result ); } } String[] bits = str.split( "," ); for ( String b: bits ){ b = b.trim(); if ( b.length() > 0 ){ result.add( b ); } } } } return( result ); } public Set<String> getAutoInstallPluginIDs() { Set<String> result = new HashSet<String>(); Map m = getMostRecentVersionCheckData(); if ( m != null ){ byte[] x = (byte[])m.get( "autoinstall_pids" ); if ( x != null ){ String str = new String( x ); String latest = COConfigurationManager.getStringParameter( "vc.autoinstall_pids.latest", "" ); if ( !str.equals( latest )){ byte[] sig = (byte[])m.get( "autoinstall_pids_sig" ); if ( sig == null ){ Debug.out( "autoinstall plugins sig missing" ); return( result ); } try{ AEVerifier.verifyData( str, sig ); COConfigurationManager.setParameter( "vc.autoinstall_pids.latest", str ); }catch( Throwable e ){ return( result ); } } String[] bits = str.split( "," ); for ( String b: bits ){ b = b.trim(); if ( b.length() > 0 ){ result.add( b ); } } } } return( result ); } /** * Get the ip address seen by the version check server. * NOTE: This information may be cached, see getVersionCheckInfo(). * @return external ip address, or empty string if no address information found */ public String getExternalIpAddress( boolean only_if_cached, boolean v6 ) { return( getExternalIpAddress( only_if_cached, v6, false )); } public String getExternalIpAddress( boolean only_if_cached, boolean v6, boolean force ) { Map reply = getVersionCheckInfoSupport( REASON_EXTERNAL_IP, only_if_cached, force, v6 ); byte[] address = (byte[])reply.get( "source_ip_address" ); if( address != null ) { return new String( address ); } return( null ); } /** * Is the DHT plugin allowed to be enabled. * @return true if DHT can be enabled, false if it should not be enabled */ public boolean DHTEnableAllowed() { Map reply = getVersionCheckInfo( REASON_DHT_ENABLE_ALLOWED, AT_EITHER ); boolean res = false; byte[] value = (byte[])reply.get( "enable_dht" ); if( value != null ) { res = new String( value ).equalsIgnoreCase( "true" ); } // we take the view that if the version check failed then we go ahead // and enable the DHT (i.e. we're being optimistic) if ( !res ){ res = !isVersionCheckDataValid( AT_EITHER ); } return res; } /** * Is the DHT allowed to be used by external plugins. * @return true if extended DHT use is allowed, false if not allowed */ public boolean DHTExtendedUseAllowed() { Map reply = getVersionCheckInfo( REASON_DHT_EXTENDED_ALLOWED, AT_EITHER ); boolean res = false; byte[] value = (byte[])reply.get( "enable_dht_extended_use" ); if( value != null ) { res = new String( value ).equalsIgnoreCase( "true" ); } // be generous and enable extended use if check failed if ( !res ){ res = !isVersionCheckDataValid( AT_EITHER ); } return res; } public String[] getRecommendedPlugins() { Map reply = getVersionCheckInfo( REASON_RECOMMENDED_PLUGINS, AT_EITHER ); List l = (List)reply.get( "recommended_plugins" ); if ( l == null ){ return( new String[0] ); } String[] res = new String[l.size()]; for (int i=0;i<l.size();i++){ res[i] = new String((byte[])l.get(i)); } return( res ); } public List<InetSocketAddress> getDHTBootstrap( boolean ipv4 ) { List<InetSocketAddress> result = new ArrayList<InetSocketAddress>(); try{ Map reply = getVersionCheckInfo( REASON_DHT_BOOTSTRAP, ipv4?AT_V4:AT_V6 ); List<Map> l = (List<Map>)reply.get( "dht_boot" ); if ( l != null ){ for( Map m: l ){ byte[] address = (byte[])m.get("a"); int port = ((Long)m.get("p")).intValue(); if ( ipv4 && address.length == 4 || !ipv4 && address.length == 16 ){ InetAddress iaddress = InetAddress.getByAddress( address ); if ( !( iaddress.isLoopbackAddress() || iaddress.isLinkLocalAddress() || iaddress.isSiteLocalAddress())){ result.add( new InetSocketAddress( iaddress, port )); } } } } }catch( Throwable e ){ Debug.out( e ); } return( result ); } public Map<String,Object> getCountryInfo() { Map reply = getVersionCheckInfo( REASON_EXTERNAL_IP, AT_EITHER ); Map<String,Object> info = (Map<String,Object>)reply.get( "source_info" ); if ( info == null ){ return( new HashMap<String,Object>()); }else{ return( BDecoder.decodeStrings( info )); } } /** * Perform the actual version check by connecting to the version server. * @param data_to_send version message * @return version reply * @throws Exception if the server check connection fails */ private Map performVersionCheck( Map data_to_send, boolean use_az_message, boolean use_http, boolean v6 ) throws Exception { Exception error = null; Map reply = null; if ( use_http ){ try{ reply = executeHTTP( data_to_send, v6 ); reply.put( "protocol_used", "HTTP" ); error = null; } catch (IOException e) { error = e; } catch (Exception e){ Debug.printStackTrace(e); error = e; } } if ( reply == null && use_az_message ){ try{ reply = executeAZMessage( data_to_send, v6 ); reply.put( "protocol_used", "AZMSG" ); error = null; } catch (IOException e) { error = e; } catch (Exception e) { Debug.printStackTrace( e ); error = e; } } if ( error != null ){ throw( error ); } if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "VersionCheckClient server " + "version check successful. Received " + (reply==null?"null":reply.size()) + " reply keys.")); if ( v6 ){ last_check_time_v6 = SystemTime.getCurrentTime(); }else{ last_check_time_v4 = SystemTime.getCurrentTime(); } return reply; } private Map executeAZMessage( Map data_to_send, boolean v6 ) throws Exception { if ( v6 && !enable_v6 ){ throw( new Exception( "IPv6 is disabled" )); } String host = getHost( v6, AZ_MSG_SERVER_ADDRESS_V6, AZ_MSG_SERVER_ADDRESS_V4 ); if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "VersionCheckClient retrieving " + "version information from " + host + ":" + AZ_MSG_SERVER_PORT)); ClientMessageService msg_service = null; Map reply = null; try{ msg_service = ClientMessageServiceClient.getServerService( host, AZ_MSG_SERVER_PORT, 20, MESSAGE_TYPE_ID ); msg_service.sendMessage( data_to_send ); //send our version message reply = msg_service.receiveMessage(); //get the server reply preProcessReply( reply, v6 ); }finally{ if ( msg_service != null ){ msg_service.close(); } } return( reply ); } private Map executeHTTP( Map data_to_send, boolean v6 ) throws Exception { if ( v6 && !enable_v6 ){ throw( new Exception( "IPv6 is disabled" )); } String host = getHost(v6, HTTP_SERVER_ADDRESS_V6, HTTP_SERVER_ADDRESS_V4 ); if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "VersionCheckClient retrieving " + "version information from " + host + ":" + HTTP_SERVER_PORT + " via HTTP" )); String url_str = "http://" + (v6?UrlUtils.convertIPV6Host(host):host) + (HTTP_SERVER_PORT==80?"":(":" + HTTP_SERVER_PORT)) + "/version?"; url_str += URLEncoder.encode( new String( BEncoder.encode( data_to_send ), "ISO-8859-1" ), "ISO-8859-1" ); URL url = new URL( url_str ); try{ HttpURLConnection url_connection = (HttpURLConnection)url.openConnection(); url_connection.setConnectTimeout( 10*1000 ); url_connection.setReadTimeout( 10*1000 ); url_connection.connect(); try{ InputStream is = url_connection.getInputStream(); Map reply = BDecoder.decode( new BufferedInputStream( is )); preProcessReply( reply, v6 ); return( reply ); }finally{ url_connection.disconnect(); } }catch( Exception e ){ if ( !v6 ){ PluginProxy proxy = AEProxyFactory.getPluginProxy( "Vuze version check", url ); if ( proxy != null ){ boolean worked = false; try{ HttpURLConnection url_connection = (HttpURLConnection)proxy.getURL().openConnection( proxy.getProxy()); url_connection.setConnectTimeout( 30*1000 ); url_connection.setReadTimeout( 30*1000 ); url_connection.connect(); try{ InputStream is = url_connection.getInputStream(); Map reply = BDecoder.decode( new BufferedInputStream( is )); preProcessReply( reply, v6 ); worked = true; return( reply ); }finally{ url_connection.disconnect(); } }finally{ proxy.setOK( worked ); } } } throw( e ); } } public String getHTTPGetString( boolean for_proxy, boolean v6 ) { return( getHTTPGetString( new HashMap(), for_proxy, v6 )); } private String getHTTPGetString( Map content, boolean for_proxy, boolean v6 ) { String host = getHost( v6, HTTP_SERVER_ADDRESS_V6, HTTP_SERVER_ADDRESS_V4 ); String get_str = "GET " + (for_proxy?("http://" + (v6?UrlUtils.convertIPV6Host( host ):host ) + ":" + HTTP_SERVER_PORT ):"") +"/version?"; try{ get_str += URLEncoder.encode( new String( BEncoder.encode( content ), "ISO-8859-1" ), "ISO-8859-1" ); }catch( Throwable e ){ } get_str +=" HTTP/1.1" + "\015\012" + "\015\012"; return( get_str ); } private Map executeTCP( Map data_to_send, InetAddress bind_ip, int bind_port, boolean v6 ) throws Exception { if ( v6 && !enable_v6 ){ throw( new Exception( "IPv6 is disabled" )); } String host = getHost(v6, TCP_SERVER_ADDRESS_V6, TCP_SERVER_ADDRESS_V4 ); if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "VersionCheckClient retrieving " + "version information from " + host + ":" + TCP_SERVER_PORT + " via TCP" )); String get_str = getHTTPGetString( data_to_send, false, v6 ); Socket socket = null; try{ socket = new Socket(); if ( bind_ip != null ){ socket.bind( new InetSocketAddress( bind_ip, bind_port )); }else if ( bind_port != 0 ){ socket.bind( new InetSocketAddress( bind_port )); } socket.setSoTimeout( 10000 ); socket.connect( new InetSocketAddress( host, TCP_SERVER_PORT ), 10000 ); OutputStream os = socket.getOutputStream(); os.write( get_str.getBytes( "ISO-8859-1" )); os.flush(); InputStream is = socket.getInputStream(); byte[] buffer = new byte[1]; String header = ""; int content_length = -1; while( true ){ int len = is.read( buffer ); if ( len <= 0 ){ break; } header += (char)buffer[0]; if ( header.endsWith( "\r\n\r\n" )){ header = header.toLowerCase( MessageText.LOCALE_ENGLISH ); int pos = header.indexOf( "content-length:" ); if ( pos == -1 ){ throw( new IOException( "content length missing" )); } header = header.substring( pos+15 ); pos = header.indexOf( '\r' ); header = header.substring(0,pos).trim(); content_length = Integer.parseInt( header ); if ( content_length > 10000 ){ throw( new IOException( "content length too large" )); } break; } if ( header.length() > 2048 ){ throw( new IOException( "header too large" )); } } ByteArrayOutputStream baos = new ByteArrayOutputStream( content_length ); buffer = new byte[content_length]; while( content_length > 0 ){ int len = is.read( buffer ); if ( len <= 0 ){ break; } baos.write( buffer, 0, len ); content_length -= len; } if ( content_length != 0 ){ throw( new IOException( "error reading reply" )); } byte[] reply_bytes = baos.toByteArray(); Map reply = BDecoder.decode( new BufferedInputStream( new ByteArrayInputStream( reply_bytes ))); preProcessReply( reply, v6 ); return( reply ); }finally{ if ( socket != null ){ try{ socket.close(); }catch( Throwable e ){ } } } } private Map executeUDP( Map data_to_send, InetAddress bind_ip, int bind_port, boolean v6 ) throws Exception { if ( v6 && !enable_v6 ){ throw( new Exception( "IPv6 is disabled" )); } String host = getHost( v6, UDP_SERVER_ADDRESS_V6, UDP_SERVER_ADDRESS_V4 ); PRUDPReleasablePacketHandler handler = PRUDPPacketHandlerFactory.getReleasableHandler( bind_port ); PRUDPPacketHandler packet_handler = handler.getHandler(); long timeout = 5; Random random = new Random(); try{ Exception last_error = null; packet_handler.setExplicitBindAddress( bind_ip ); for (int i=0;i<3;i++){ try{ // connection ids for requests must always have their msb set... // apart from the original darn udp tracker spec.... long connection_id = 0x8000000000000000L | random.nextLong(); VersionCheckClientUDPRequest request_packet = new VersionCheckClientUDPRequest( connection_id ); request_packet.setPayload( data_to_send ); VersionCheckClientUDPReply reply_packet = (VersionCheckClientUDPReply)packet_handler.sendAndReceive( null, request_packet, new InetSocketAddress( host, UDP_SERVER_PORT ), timeout ); Map reply = reply_packet.getPayload(); preProcessReply( reply, v6 ); return( reply ); }catch( Exception e){ last_error = e; timeout = timeout * 2; } } if ( last_error != null ){ throw( last_error ); } throw( new Exception( "Timeout" )); }finally{ packet_handler.setExplicitBindAddress( null ); handler.release(); } } protected void preProcessReply( Map reply, final boolean v6 ) { NetworkAdmin admin = NetworkAdmin.getSingleton(); try{ byte[] address = (byte[])reply.get( "source_ip_address" ); if ( address != null ){ InetAddress my_ip = InetAddress.getByName( new String( address )); NetworkAdminASN old_asn = admin.getCurrentASN(); NetworkAdminASN new_asn = admin.lookupCurrentASN( my_ip ); if ( !new_asn.sameAs( old_asn )){ // kick off a secondary version check to communicate the new information if ( !secondary_check_done ){ secondary_check_done = true; new AEThread( "Secondary version check", true ) { public void runSupport() { getVersionCheckInfoSupport( REASON_SECONDARY_CHECK, false, true, v6 ); } }.start(); } } } }catch( Throwable e ){ if ( !Debug.containsException( e, UnknownHostException.class )){ Debug.printStackTrace(e); } } Long as_advice = (Long)reply.get( "as_advice" ); if ( as_advice != null ){ NetworkAdminASN current_asn = admin.getCurrentASN(); String asn = current_asn.getASName(); if ( asn != null ){ long advice = as_advice.longValue(); if ( advice != 0 ){ // require crypto String done_asn = COConfigurationManager.getStringParameter( "ASN Advice Followed", "" ); if ( !done_asn.equals( asn )){ COConfigurationManager.setParameter( "ASN Advice Followed", asn ); boolean change = advice == 1 || advice == 2; boolean alert = advice == 1 || advice == 3; if ( !COConfigurationManager.getBooleanParameter( "network.transport.encrypted.require" )){ if ( change ){ COConfigurationManager.setParameter( "network.transport.encrypted.require", true ); } if ( alert ){ String msg = MessageText.getString( "crypto.alert.as.warning", new String[]{ asn }); Logger.log( new LogAlert( false, LogAlert.AT_WARNING, msg )); } } } } } } // set ui.toolbar.uiswitcher based on instructions from tracker // Really shouldn't be in VersionCheck client, but instead have some // listener and have the code elsewhere. Simply calling //getVersionCheckInfo from "code elsewhere" (to get the cached result) //caused a deadlock at startup. Long lEnabledUISwitcher = (Long) reply.get("ui.toolbar.uiswitcher"); if (lEnabledUISwitcher != null) { COConfigurationManager.setBooleanDefault("ui.toolbar.uiswitcher", lEnabledUISwitcher.longValue() == 1); } } public InetAddress getExternalIpAddressHTTP( boolean v6 ) throws Exception { Map reply = executeHTTP( new HashMap(), v6 ); byte[] address = (byte[])reply.get( "source_ip_address" ); return( address==null?null:InetAddress.getByName( new String( address ))); } public InetAddress getExternalIpAddressTCP( InetAddress bind_ip, int bind_port, boolean v6 ) throws Exception { Map reply = executeTCP( new HashMap(), bind_ip, bind_port, v6 ); byte[] address = (byte[])reply.get( "source_ip_address" ); return( address==null?null:InetAddress.getByName( new String( address ))); } public InetAddress getExternalIpAddressUDP( InetAddress bind_ip, int bind_port, boolean v6 ) throws Exception { Map reply = executeUDP( new HashMap(), bind_ip, bind_port, v6 ); byte[] address = (byte[])reply.get( "source_ip_address" ); return( address==null?null:InetAddress.getByName( new String( address ))); } protected String getHost( boolean v6, String v6_address, String v4_address ) { if ( v6 ){ v6_address = getTestAddress( true, v6_address ); try{ return( InetAddress.getByName( v6_address ).getHostAddress()); }catch( UnknownHostException e ){ DNSUtils.DNSUtilsIntf dns_utils = DNSUtils.getSingleton(); if ( dns_utils != null ){ try{ return( dns_utils.getIPV6ByName( v6_address ).getHostAddress()); }catch( UnknownHostException f ){ } } return( v6_address ); } }else{ v4_address = getTestAddress( false, v4_address ); return( v4_address ); } } private String getTestAddress( boolean v6, String address ) { return( COConfigurationManager.getStringParameter( "versioncheck.test.address." + v6, address )); } /** * Construct the default version check message. * @return message to send */ public static Map<String,Object> constructVersionCheckMessage( String reason ) { //only send if anonymous-check flag is not set boolean send_info = COConfigurationManager.getBooleanParameter( "Send Version Info" ); Map<String,Object> message = new HashMap<String,Object>(); //always send message.put( "appid", SystemProperties.getApplicationIdentifier()); message.put( "appname", SystemProperties.getApplicationName()); message.put( "version", Constants.AZUREUS_VERSION ); String sub_ver = Constants.AZUREUS_SUBVER; if ( sub_ver.length() > 0 ){ message.put( "subver", sub_ver ); } if ( COConfigurationManager.getBooleanParameter( "Beta Programme Enabled" )){ message.put( "beta_prog", "true" ); } message.put( "ui", COConfigurationManager.getStringParameter( "ui", "unknown" ) ); message.put( "os", Constants.OSName ); message.put( "os_version", System.getProperty( "os.version" ) ); message.put( "os_arch", System.getProperty( "os.arch" ) ); //see http://lopica.sourceforge.net/os.html message.put( "os_arch_dm", System.getProperty( "sun.arch.data.model" ) ); // might be needed to openjdk on osx boolean using_phe = COConfigurationManager.getBooleanParameter( "network.transport.encrypted.require" ); message.put( "using_phe", using_phe ? new Long(1) : new Long(0) ); message.put( "imode", COConfigurationManager.getStringParameter( "installer.mode", "" )); //swt stuff try { Class c = Class.forName( "org.eclipse.swt.SWT" ); String swt_platform = (String)c.getMethod( "getPlatform", new Class[]{} ).invoke( null, new Object[]{} ); message.put( "swt_platform", swt_platform ); Integer swt_version = (Integer)c.getMethod( "getVersion", new Class[]{} ).invoke( null, new Object[]{} ); message.put( "swt_version", new Long( swt_version.longValue() ) ); } catch( ClassNotFoundException e ) { /* ignore */ } catch( NoClassDefFoundError er ) { /* ignore */ } catch( InvocationTargetException err ) { /* ignore */ } catch( Throwable t ) { t.printStackTrace(); } int last_send_time = COConfigurationManager.getIntParameter( "Send Version Info Last Time", -1 ); int current_send_time = (int)(SystemTime.getCurrentTime()/1000); COConfigurationManager.setParameter( "Send Version Info Last Time", current_send_time ); String id = COConfigurationManager.getStringParameter( "ID", null ); if( id != null && send_info ) { message.put( "id", id ); try{ byte[] id2 = CryptoManagerFactory.getSingleton().getSecureID(); message.put( "id2", id2 ); }catch( Throwable e ){ } if ( last_send_time != -1 && last_send_time < current_send_time ){ // time since last message.put( "tsl", new Long(current_send_time-last_send_time)); } message.put( "reason", reason ); String java_version = Constants.JAVA_VERSION; if ( java_version == null ){ java_version = "unknown"; } message.put( "java", java_version ); String java_vendor = System.getProperty( "java.vm.vendor" ); if ( java_vendor == null ){ java_vendor = "unknown"; } message.put( "javavendor", java_vendor ); int api_level = Constants.API_LEVEL; if ( api_level > 0 ){ message.put( "api_level", api_level ); } long max_mem = Runtime.getRuntime().maxMemory()/(1024*1024); message.put( "javamx", new Long( max_mem ) ); String java_rt_name = System.getProperty("java.runtime.name"); if (java_rt_name != null) { message.put( "java_rt_name", java_rt_name); } String java_rt_version = System.getProperty("java.runtime.version"); if (java_rt_version != null) { message.put( "java_rt_version", java_rt_version); } OverallStats stats = StatsFactory.getStats(); if ( stats != null ){ //long total_bytes_downloaded = stats.getDownloadedBytes(); //long total_bytes_uploaded = stats.getUploadedBytes(); long total_uptime = stats.getTotalUpTime(); //removed due to complaints about anonymous stats collection //message.put( "total_bytes_downloaded", new Long( total_bytes_downloaded ) ); //message.put( "total_bytes_uploaded", new Long( total_bytes_uploaded ) ); message.put( "total_uptime", new Long( total_uptime ) ); //message.put( "dlstats", stats.getDownloadStats()); } try{ int port = UDPNetworkManager.getSingleton().getUDPNonDataListeningPortNumber(); message.put( "dht", port ); }catch( Throwable e ){ Debug.out( e ); } try{ NetworkAdminASN current_asn = NetworkAdmin.getSingleton().getCurrentASN(); message.put( "ip_as", current_asn.getAS()); String asn = current_asn.getASName(); if ( asn.length() > 64 ){ asn = asn.substring( 0, 64 ); } message.put( "ip_asn", asn ); }catch( Throwable e ){ Debug.out( e ); } // send locale, so we can determine which languages need attention message.put("locale", Locale.getDefault().toString()); String originalLocale = System.getProperty("user.language") + "_" + System.getProperty("user.country"); String variant = System.getProperty("user.variant"); if (variant != null && variant.length() > 0) { originalLocale += "_" + variant; } message.put("orig_locale", originalLocale); // We may want to reply differently if the user is in Beginner mode vs Advanced message.put("user_mode", COConfigurationManager.getIntParameter("User Mode", -1)); Set<String> features = UtilitiesImpl.getFeaturesInstalled(); if ( features.size() > 0 ){ String str = ""; for ( String f: features ){ str += (str.length()==0?"":",") + f; } message.put( "vzfeatures", str ); } try{ if ( AzureusCoreFactory.isCoreAvailable() && AzureusCoreFactory.getSingleton().getPluginManager().isInitialized()){ //installed plugin IDs PluginInterface[] plugins = AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaces(); List pids = new ArrayList(); List vs_data = new ArrayList(); for (int i=0;i<plugins.length;i++){ PluginInterface plugin = plugins[i]; String pid = plugin.getPluginID(); String info = plugin.getPluginconfig().getPluginStringParameter( "plugin.info" ); // filter out built-in and core ones if ( ( info != null && info.length() > 0 ) || ( !pid.startsWith( "<" ) && !pid.startsWith( "azbp" ) && !pid.startsWith( "azupdater" ) && !pid.startsWith( "azplatform" ) && !pids.contains( pid ))){ if ( info != null && info.length() > 0 ){ if( info.length() < 256 ){ pid += ":" + info; }else{ Debug.out( "Plugin '" + pid + "' reported excessive info string '" + info + "'" ); } } pids.add( pid ); } Map data = plugin.getPluginconfig().getPluginMapParameter( "plugin.versionserver.data", null ); if ( data != null ){ Map payload = new HashMap(); byte[] data_bytes = BEncoder.encode( data ); if ( data_bytes.length > 16*1024 ){ Debug.out( "Plugin '" + pid + "' reported excessive version server data (length=" + data_bytes.length + ")" ); payload.put( "error", "data too long: " + data_bytes.length ); }else{ payload.put( "data", data_bytes ); } payload.put( "id", pid); payload.put( "version", plugin.getPluginVersion()); vs_data.add( payload ); } } message.put( "plugins", pids ); if ( vs_data.size() > 0 ){ message.put( "plugin_data", vs_data ); } } }catch( Throwable e ){ Debug.out( e ); } } return message; } public void addVersionCheckClientListener( boolean triggerStartListener, VersionCheckClientListener l) { synchronized (listeners) { listeners.add(l); if (triggerStartListener && startCheckRan) { try { l.versionCheckStarted(REASON_UPDATE_CHECK_START); } catch (Exception e) { Debug.out(e); } } } } public void removeVersionCheckClientListener( VersionCheckClientListener l) { synchronized (listeners) { listeners.remove(l); } } public static void main( String[] args ) { try{ COConfigurationManager.initialise(); COConfigurationManager.setParameter( "IPV6 Enable Support", true ); boolean v6= true; // Test connectivity. if (true) { // System.out.println( "UDP: " + getSingleton().getExternalIpAddressUDP(null,0,v6)); // System.out.println( "TCP: " + getSingleton().getExternalIpAddressTCP(null,0,v6)); System.out.println( "HTTP: " + getSingleton().getExternalIpAddressHTTP(v6)); } Map data = constructVersionCheckMessage(VersionCheckClient.REASON_UPDATE_CHECK_START); System.out.println("Sending (pre-initialisation):"); printDataMap(data); System.out.println("-----------"); System.out.println("Receiving (pre-initialisation):"); printDataMap(getSingleton().getVersionCheckInfo(VersionCheckClient.REASON_UPDATE_CHECK_START)); System.out.println("-----------"); System.out.println(); System.out.print("Initialising core... "); /** * Suppress all of these errors being displayed in the output. * * These things should be temporary... */ AzureusCoreImpl.SUPPRESS_CLASSLOADER_ERRORS = true; DownloadManagerStateImpl.SUPPRESS_FIXUP_ERRORS = true; AzureusCore core = AzureusCoreFactory.create(); core.start(); System.out.println("done."); System.out.println(); System.out.println("-----------"); data = constructVersionCheckMessage(VersionCheckClient.REASON_UPDATE_CHECK_START); System.out.println("Sending (post-initialisation):"); printDataMap(data); System.out.println("-----------"); System.out.println("Receiving (post-initialisation):"); printDataMap(getSingleton().getVersionCheckInfo(VersionCheckClient.REASON_UPDATE_CHECK_START)); System.out.println("-----------"); System.out.println(); System.out.print("Shutting down core... "); core.stop(); System.out.println("done."); }catch( Throwable e){ e.printStackTrace(); } } // Used for debugging in main. private static void printDataMap(Map map) throws Exception { TreeMap res = new TreeMap(map); Iterator key_itr = map.keySet().iterator(); while (key_itr.hasNext()) { Object key = key_itr.next(); Object val = map.get(key); if (val instanceof byte[]) { String as_bytes = ByteFormatter.nicePrint((byte[])val); String as_text = new String((byte[])val, Constants.BYTE_ENCODING); res.put(key, as_text + " [" + as_bytes + "]"); } } Iterator entries = res.entrySet().iterator(); Map.Entry entry; while (entries.hasNext()) { entry = (Map.Entry)entries.next(); System.out.print(" "); System.out.print(entry.getKey()); System.out.print(": "); System.out.print(entry.getValue()); System.out.println(); } } }