1 /*
2  * Created on Dec 17, 2013
3  * Created by Paul Gardner
4  *
5  * Copyright 2013 Azureus Software, Inc.  All rights reserved.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18  */
19 
20 
21 package com.aelitis.azureus.core.proxy.impl;
22 
23 import java.lang.ref.WeakReference;
24 import java.net.InetSocketAddress;
25 import java.net.Proxy;
26 import java.net.URL;
27 import java.util.*;
28 
29 import org.gudy.azureus2.core3.config.COConfigurationManager;
30 import org.gudy.azureus2.core3.config.ParameterListener;
31 import org.gudy.azureus2.core3.util.AENetworkClassifier;
32 import org.gudy.azureus2.core3.util.AESemaphore;
33 import org.gudy.azureus2.core3.util.Debug;
34 import org.gudy.azureus2.core3.util.SystemTime;
35 import org.gudy.azureus2.core3.util.TorrentUtils;
36 import org.gudy.azureus2.core3.util.UrlUtils;
37 import org.gudy.azureus2.plugins.PluginAdapter;
38 import org.gudy.azureus2.plugins.PluginEvent;
39 import org.gudy.azureus2.plugins.PluginEventListener;
40 import org.gudy.azureus2.plugins.PluginInterface;
41 import org.gudy.azureus2.plugins.ipc.IPCInterface;
42 import org.gudy.azureus2.pluginsimpl.local.PluginInitializer;
43 
44 import com.aelitis.azureus.core.AzureusCore;
45 import com.aelitis.azureus.core.AzureusCoreFactory;
46 import com.aelitis.azureus.core.proxy.AEProxySelectorFactory;
47 import com.aelitis.azureus.core.proxy.AEProxyFactory.PluginHTTPProxy;
48 import com.aelitis.azureus.core.proxy.AEProxyFactory.PluginProxy;
49 import com.aelitis.azureus.core.util.CopyOnWriteList;
50 import com.aelitis.azureus.plugins.dht.DHTPluginInterface;
51 
52 public class
53 AEPluginProxyHandler
54 {
55 	private static CopyOnWriteList<PluginInterface>		plugins = new CopyOnWriteList<PluginInterface>();
56 
57 	private static final int			plugin_init_max_wait	= 30*1000;
58 	private static final AESemaphore 	plugin_init_complete 	= new AESemaphore( "init:waiter" );
59 
60 	private static boolean	enable_plugin_proxies_with_socks;
61 
62 	static{
63 		try{
64 			COConfigurationManager.addAndFireParameterListener(
65 				"Proxy.SOCKS.disable.plugin.proxies",
66 				new ParameterListener() {
67 					public void
68 					parameterChanged(
69 						String parameterName)
70 					{
71 						enable_plugin_proxies_with_socks = !COConfigurationManager.getBooleanParameter( parameterName );
72 					}
73 				});
74 
75 			AzureusCore core = AzureusCoreFactory.getSingleton();
76 
77 			PluginInterface default_pi = core.getPluginManager().getDefaultPluginInterface();
78 
default_pi.addEventListener( new PluginEventListener() { public void handleEvent( PluginEvent ev ) { int type = ev.getType(); if ( type == PluginEvent.PEV_PLUGIN_OPERATIONAL ){ pluginAdded((PluginInterface)ev.getValue()); } if ( type == PluginEvent.PEV_PLUGIN_NOT_OPERATIONAL ){ pluginRemoved((PluginInterface)ev.getValue()); } } })79 			default_pi.addEventListener(
80 					new PluginEventListener()
81 					{
82 						public void
83 						handleEvent(
84 							PluginEvent ev )
85 						{
86 							int	type = ev.getType();
87 
88 							if ( type == PluginEvent.PEV_PLUGIN_OPERATIONAL ){
89 
90 								pluginAdded((PluginInterface)ev.getValue());
91 							}
92 							if ( type == PluginEvent.PEV_PLUGIN_NOT_OPERATIONAL ){
93 
94 								pluginRemoved((PluginInterface)ev.getValue());
95 							}
96 						}
97 					});
98 
99 				PluginInterface[] plugins = default_pi.getPluginManager().getPlugins( true );
100 
101 				for ( PluginInterface pi: plugins ){
102 
103 					if ( pi.getPluginState().isOperational()){
104 
105 						pluginAdded( pi );
106 					}
107 				}
108 
default_pi.addListener( new PluginAdapter() { public void initializationComplete() { plugin_init_complete.releaseForever(); } })109 				default_pi.addListener(
110 					new PluginAdapter()
111 					{
112 						public void
113 						initializationComplete()
114 						{
115 							plugin_init_complete.releaseForever();
116 						}
117 					});
118 
119 		}catch( Throwable e ){
120 
121 			e.printStackTrace();
122 		}
123 	}
124 
125 	private static void
pluginAdded( PluginInterface pi )126 	pluginAdded(
127 		PluginInterface pi )
128 	{
129 		String pid = pi.getPluginID();
130 
131 		if ( pid.equals( "aznettor" ) || pid.equals( "azneti2phelper" )){
132 
133 			plugins.add( pi );
134 		}
135 	}
136 
137 	private static void
pluginRemoved( PluginInterface pi )138 	pluginRemoved(
139 		PluginInterface pi )
140 	{
141 		String pid = pi.getPluginID();
142 
143 		if ( pid.equals( "aznettor" ) || pid.equals( "azneti2phelper" )){
144 
145 			plugins.remove( pi );
146 		}
147 	}
148 
149 	private static boolean
waitForPlugins( int max_wait )150 	waitForPlugins(
151 		int		max_wait )
152 	{
153 		if ( PluginInitializer.isInitThread()){
154 
155 			Debug.out( "Hmm, rework this" );
156 		}
157 
158 		return( plugin_init_complete.reserve( max_wait ));
159 	}
160 
161 	private static final Map<Proxy,WeakReference<PluginProxyImpl>>	proxy_map = new IdentityHashMap<Proxy,WeakReference<PluginProxyImpl>>();
162 
163 	public static boolean
hasPluginProxyForNetwork( String network, boolean supports_data )164 	hasPluginProxyForNetwork(
165 		String		network,
166 		boolean		supports_data )
167 	{
168 		long start = SystemTime.getMonotonousTime();
169 
170 		while( true ){
171 
172 			long	rem = plugin_init_max_wait - ( SystemTime.getMonotonousTime() - start );
173 
174 			if ( rem <= 0 ){
175 
176 				return( false );
177 			}
178 
179 			boolean wait_complete = waitForPlugins( Math.min( (int)rem, 1000 ));
180 
181 			boolean result = getPluginProxyForNetwork( network, supports_data ) != null;
182 
183 			if ( result || wait_complete ){
184 
185 				return( result );
186 			}
187 		}
188 	}
189 
190 	private static PluginInterface
getPluginProxyForNetwork( String network, boolean supports_data )191 	getPluginProxyForNetwork(
192 		String		network,
193 		boolean		supports_data )
194 	{
195 		for ( PluginInterface pi: plugins ){
196 
197 			String pid = pi.getPluginID();
198 
199 			if ( pid.equals( "aznettor" ) && network == AENetworkClassifier.AT_TOR ){
200 
201 				if ( !supports_data ){
202 
203 					return( pi );
204 				}
205 			}
206 
207 			if ( pid.equals( "azneti2phelper" ) && network == AENetworkClassifier.AT_I2P ){
208 
209 				return( pi );
210 			}
211 		}
212 
213 		return( null );
214 	}
215 
216 	public static boolean
hasPluginProxy()217 	hasPluginProxy()
218 	{
219 		waitForPlugins( plugin_init_max_wait );
220 
221 		for ( PluginInterface pi: plugins ){
222 
223 			try{
224 				IPCInterface ipc = pi.getIPC();
225 
226 				if ( ipc.canInvoke( "testHTTPPseudoProxy", new Object[]{ TorrentUtils.getDecentralisedEmptyURL() })){
227 
228 					return( true );
229 				}
230 			}catch( Throwable e ){
231 			}
232 		}
233 
234 		return( false );
235 	}
236 
237 	private static boolean
isEnabled()238 	isEnabled()
239 	{
240 		Proxy system_proxy = AEProxySelectorFactory.getSelector().getActiveProxy();
241 
242 		if ( system_proxy == null || system_proxy.equals( Proxy.NO_PROXY )){
243 
244 			return( true );
245 
246 		}else{
247 
248 			return( enable_plugin_proxies_with_socks );
249 		}
250 	}
251 
252 		/**
253 		 * This method should NOT BE CALLED as it is in the .impl package - unfortunately the featman plugin calls it - will be removed
254 		 * when aefeatman 1.3.2 is released
255 		 * @param reason
256 		 * @param target
257 		 * @deprecated
258 		 * @return
259 		 */
260 
261 	public static PluginProxyImpl
getPluginProxy( String reason, URL target )262 	getPluginProxy(
263 		String	reason,
264 		URL		target )
265 	{
266 		return( getPluginProxy( reason, target, null, false ));
267 	}
268 
269 	public static PluginProxyImpl
getPluginProxy( String reason, URL target, Map<String,Object> properties, boolean can_wait )270 	getPluginProxy(
271 		String					reason,
272 		URL						target,
273 		Map<String,Object>		properties,
274 		boolean					can_wait )
275 	{
276 		if ( isEnabled()){
277 
278 			String url_protocol = target.getProtocol().toLowerCase();
279 
280 			if ( url_protocol.startsWith( "http" ) || url_protocol.equals( "ftp" )){
281 
282 				if ( can_wait ){
283 
284 					waitForPlugins(0);
285 				}
286 
287 				if ( properties == null ){
288 
289 					properties = new HashMap<String, Object>();
290 				}
291 
292 				for ( PluginInterface pi: plugins ){
293 
294 					try{
295 						IPCInterface ipc = pi.getIPC();
296 
297 						Object[] proxy_details;
298 
299 						if ( ipc.canInvoke( "getProxy", new Object[]{ reason, target, properties } )){
300 
301 							proxy_details = (Object[])ipc.invoke( "getProxy", new Object[]{ reason, target, properties } );
302 
303 						}else{
304 
305 							proxy_details = (Object[])ipc.invoke( "getProxy", new Object[]{ reason, target } );
306 						}
307 
308 						if ( proxy_details != null ){
309 
310 							if ( proxy_details.length == 2 ){
311 
312 									// support old plugins
313 
314 								proxy_details = new Object[]{ proxy_details[0], proxy_details[1], target.getHost()};
315 							}
316 
317 							return( new PluginProxyImpl( target.toExternalForm(), reason, ipc, properties, proxy_details ));
318 						}
319 					}catch( Throwable e ){
320 					}
321 				}
322 			}
323 		}
324 
325 		return( null );
326 	}
327 
328 	public static PluginProxyImpl
getPluginProxy( String reason, String host, int port, Map<String,Object> properties )329 	getPluginProxy(
330 		String					reason,
331 		String					host,
332 		int						port,
333 		Map<String,Object>		properties )
334 	{
335 		if ( isEnabled()){
336 
337 			if ( properties == null ){
338 
339 				properties = new HashMap<String, Object>();
340 			}
341 
342 			for ( PluginInterface pi: plugins ){
343 
344 				try{
345 					IPCInterface ipc = pi.getIPC();
346 
347 					Object[] proxy_details;
348 
349 					if ( ipc.canInvoke( "getProxy", new Object[]{ reason, host, port, properties })){
350 
351 						proxy_details = (Object[])ipc.invoke( "getProxy", new Object[]{ reason, host, port, properties });
352 
353 					}else{
354 
355 						proxy_details = (Object[])ipc.invoke( "getProxy", new Object[]{ reason, host, port });
356 					}
357 
358 					if ( proxy_details != null ){
359 
360 						return( new PluginProxyImpl( host + ":" + port, reason, ipc, properties, proxy_details ));
361 					}
362 				}catch( Throwable e ){
363 				}
364 			}
365 		}
366 
367 		return( null );
368 	}
369 
370 	public static PluginProxy
getPluginProxy( Proxy proxy )371 	getPluginProxy(
372 		Proxy		proxy )
373 	{
374 		if ( proxy != null ){
375 
376 			synchronized( proxy_map ){
377 
378 				WeakReference<PluginProxyImpl>	ref = proxy_map.get( proxy );
379 
380 				if ( ref != null ){
381 
382 					return( ref.get());
383 				}
384 			}
385 		}
386 
387 		return( null );
388 	}
389 
390 	public static Boolean
testPluginHTTPProxy( URL url, boolean can_wait )391 	testPluginHTTPProxy(
392 		URL			url,
393 		boolean		can_wait )
394 	{
395 		if ( isEnabled()){
396 
397 			String url_protocol = url.getProtocol().toLowerCase();
398 
399 			if ( url_protocol.startsWith( "http" )){
400 
401 				if ( can_wait ){
402 
403 					waitForPlugins(0);
404 				}
405 
406 				for ( PluginInterface pi: plugins ){
407 
408 					try{
409 						IPCInterface ipc = pi.getIPC();
410 
411 						return((Boolean)ipc.invoke( "testHTTPPseudoProxy", new Object[]{ url }));
412 
413 					}catch( Throwable e ){
414 					}
415 				}
416 			}else{
417 
418 				Debug.out( "Unsupported protocol: " + url_protocol );
419 			}
420 		}
421 
422 		return( null );
423 	}
424 
425 	public static PluginHTTPProxyImpl
getPluginHTTPProxy( String reason, URL url, boolean can_wait )426 	getPluginHTTPProxy(
427 		String		reason,
428 		URL			url,
429 		boolean		can_wait )
430 	{
431 		if ( isEnabled()){
432 
433 			String url_protocol = url.getProtocol().toLowerCase();
434 
435 			if ( url_protocol.startsWith( "http" )){
436 
437 				if ( can_wait ){
438 
439 					waitForPlugins(0);
440 				}
441 
442 				for ( PluginInterface pi: plugins ){
443 
444 					try{
445 						IPCInterface ipc = pi.getIPC();
446 
447 						Proxy proxy = (Proxy)ipc.invoke( "createHTTPPseudoProxy", new Object[]{ reason, url });
448 
449 						if ( proxy != null ){
450 
451 							return( new PluginHTTPProxyImpl( reason, ipc, proxy ));
452 						}
453 					}catch( Throwable e ){
454 					}
455 				}
456 			}else{
457 
458 				Debug.out( "Unsupported protocol: " + url_protocol );
459 			}
460 		}
461 
462 		return( null );
463 	}
464 
465 	public static List<PluginInterface>
getPluginHTTPProxyProviders( boolean can_wait )466 	getPluginHTTPProxyProviders(
467 		boolean		can_wait )
468 	{
469 		if ( can_wait ){
470 
471 			waitForPlugins(0);
472 		}
473 
474 		List<PluginInterface> pis =
475 			AzureusCoreFactory.getSingleton().getPluginManager().getPluginsWithMethod(
476 				"createHTTPPseudoProxy",
477 				new Class[]{ String.class, URL.class });
478 
479 		return( pis );
480 	}
481 
482 	public static Map<String,Object>
getPluginServerProxy( String reason, String network, String server_uid, Map<String,Object> options )483 	getPluginServerProxy(
484 		String					reason,
485 		String					network,
486 		String					server_uid,
487 		Map<String,Object>		options )
488 	{
489 		waitForPlugins( plugin_init_max_wait );
490 
491 		PluginInterface pi = getPluginProxyForNetwork( network, false );
492 
493 		if ( pi == null ){
494 
495 			return( null );
496 		}
497 
498 		options = new HashMap<String,Object>( options );
499 
500 		options.put( "id", server_uid );
501 
502 		try{
503 			IPCInterface ipc = pi.getIPC();
504 
505 			Map<String,Object> reply = (Map<String,Object>)ipc.invoke( "getProxyServer", new Object[]{ reason, options });
506 
507 			return( reply );
508 
509 		}catch( Throwable e ){
510 
511 		}
512 
513 		return( null );
514 	}
515 
516 	public static DHTPluginInterface
getPluginDHTProxy( String reason, String network, Map<String,Object> options )517 	getPluginDHTProxy(
518 		String					reason,
519 		String					network,
520 		Map<String,Object>		options )
521 	{
522 		waitForPlugins( plugin_init_max_wait );
523 
524 		PluginInterface pi = getPluginProxyForNetwork( network, false );
525 
526 		if ( pi == null ){
527 
528 			return( null );
529 		}
530 
531 		try{
532 			IPCInterface ipc = pi.getIPC();
533 
534 			DHTPluginInterface reply = (DHTPluginInterface)ipc.invoke( "getProxyDHT", new Object[]{ reason, options });
535 
536 			return( reply );
537 
538 		}catch( Throwable e ){
539 
540 		}
541 
542 		return( null );
543 	}
544 
545 	private static class
546 	PluginProxyImpl
547 		implements PluginProxy
548 	{
549 		private final long					create_time = SystemTime.getMonotonousTime();
550 
551 		private final String				target;
552 		private final String				reason;
553 
554 		private final IPCInterface			ipc;
555 		private final Map<String,Object>	proxy_options;
556 		private final Object[]				proxy_details;
557 
558 		private List<PluginProxyImpl>	children = new ArrayList<AEPluginProxyHandler.PluginProxyImpl>();
559 
560 		private
PluginProxyImpl( String _target, String _reason, IPCInterface _ipc, Map<String,Object> _proxy_options, Object[] _proxy_details )561 		PluginProxyImpl(
562 			String				_target,
563 			String				_reason,
564 			IPCInterface		_ipc,
565 			Map<String,Object>	_proxy_options,
566 			Object[]			_proxy_details )
567 		{
568 			target				= _target;
569 			reason				= _reason;
570 			ipc					= _ipc;
571 			proxy_options		= _proxy_options;
572 			proxy_details		= _proxy_details;
573 
574 			WeakReference<PluginProxyImpl>	my_ref = new WeakReference<PluginProxyImpl>( this );
575 
576 			List<PluginProxyImpl>	removed = new ArrayList<PluginProxyImpl>();
577 
578 			synchronized( proxy_map ){
579 
580 				proxy_map.put( getProxy(), my_ref );
581 
582 				if ( proxy_map.size() > 1024 ){
583 
584 					long	now = SystemTime.getMonotonousTime();
585 
586 					Iterator<WeakReference<PluginProxyImpl>>	it = proxy_map.values().iterator();
587 
588 					while( it.hasNext()){
589 
590 						WeakReference<PluginProxyImpl> ref = it.next();
591 
592 						PluginProxyImpl	pp = ref.get();
593 
594 						if ( pp == null ){
595 
596 							it.remove();
597 
598 						}else{
599 
600 							if ( now - pp.create_time > 5*60*1000 ){
601 
602 								removed.add( pp );
603 
604 								it.remove();
605 							}
606 						}
607 					}
608 				}
609 			}
610 
611 			for ( PluginProxyImpl pp: removed ){
612 
613 				pp.setOK( false );
614 			}
615 		}
616 
617 		public String
getTarget()618 		getTarget()
619 		{
620 			return( target );
621 		}
622 
623 		public PluginProxy
getChildProxy( String child_reason, URL url)624 		getChildProxy(
625 			String		child_reason,
626 			URL 		url)
627 		{
628 			PluginProxyImpl	child = getPluginProxy( reason + " - " + child_reason, url, proxy_options, false );
629 
630 			if ( child != null ){
631 
632 				synchronized( children ){
633 
634 					children.add( child );
635 				}
636 			}
637 
638 			return( child );
639 		}
640 
641 		public Proxy
getProxy()642 		getProxy()
643 		{
644 			return((Proxy)proxy_details[0]);
645 		}
646 
647 			// URL methods
648 
649 		public URL
getURL()650 		getURL()
651 		{
652 			return((URL)proxy_details[1]);
653 		}
654 
655 		public String
getURLHostRewrite()656 		getURLHostRewrite()
657 		{
658 			return((String)proxy_details[2]);
659 		}
660 
661 			// host:port methods
662 
663 		public String
getHost()664 		getHost()
665 		{
666 			return((String)proxy_details[1]);
667 		}
668 
669 		public int
getPort()670 		getPort()
671 		{
672 			return((Integer)proxy_details[2]);
673 		}
674 
675 		public void
setOK( boolean good )676 		setOK(
677 			boolean	good )
678 		{
679 			try{
680 				ipc.invoke( "setProxyStatus", new Object[]{ proxy_details[0], good });
681 
682 			}catch( Throwable e ){
683 			}
684 
685 			List<PluginProxyImpl> kids;
686 
687 			synchronized( children ){
688 
689 				kids = new ArrayList<PluginProxyImpl>( children );
690 
691 				children.clear();
692 			}
693 
694 			for ( PluginProxyImpl child: kids ){
695 
696 				child.setOK( good );
697 			}
698 
699 			synchronized( proxy_map ){
700 
701 				proxy_map.remove( getProxy());
702 			}
703 		}
704 	}
705 
706 	private static class
707 	PluginHTTPProxyImpl
708 		implements PluginHTTPProxy
709 	{
710 		private String			reason;
711 		private IPCInterface	ipc;
712 		private Proxy			proxy;
713 
714 		private
PluginHTTPProxyImpl( String _reason, IPCInterface _ipc, Proxy _proxy )715 		PluginHTTPProxyImpl(
716 			String				_reason,
717 			IPCInterface		_ipc,
718 			Proxy				_proxy )
719 		{
720 			reason				= _reason;
721 			ipc					= _ipc;
722 			proxy				= _proxy;
723 		}
724 
725 		public Proxy
getProxy()726 		getProxy()
727 		{
728 			return( proxy );
729 		}
730 
731 		public String
proxifyURL( String url )732 		proxifyURL(
733 			String url )
734 		{
735 			try{
736 				URL _url = new URL( url );
737 
738 				InetSocketAddress pa = (InetSocketAddress)proxy.address();
739 
740 				_url = UrlUtils.setHost( _url, pa.getAddress().getHostAddress());
741 				_url = UrlUtils.setPort( _url, pa.getPort());
742 
743 				url = _url.toExternalForm();
744 
745 				url += ( url.indexOf('?')==-1?"?":"&" ) + "_azpproxy=1";
746 
747 				return( url );
748 
749 			}catch( Throwable e ){
750 
751 				Debug.out( "Failed to proxify URL: " + url, e );
752 
753 				return( url );
754 			}
755 		}
756 
757 		public void
destroy()758 		destroy()
759 		{
760 			try{
761 
762 				ipc.invoke( "destroyHTTPPseudoProxy", new Object[]{ proxy });
763 
764 			}catch( Throwable e ){
765 
766 				Debug.out( e );
767 			}
768 		}
769 	}
770 }
771