1 /*
2  * File    : WebPlugin.java
3  * Created : 23-Jan-2004
4  * By      : parg
5  *
6  * Azureus - a Java Bittorrent client
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details ( see the LICENSE file ).
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22 
23 package org.gudy.azureus2.ui.webplugin;
24 
25 /**
26  * @author parg
27  *
28  */
29 
30 import java.io.*;
31 import java.util.*;
32 import java.util.zip.GZIPOutputStream;
33 import java.net.*;
34 
35 import org.gudy.azureus2.core3.config.COConfigurationManager;
36 import org.gudy.azureus2.core3.util.*;
37 import org.gudy.azureus2.plugins.*;
38 import org.gudy.azureus2.plugins.logging.*;
39 import org.gudy.azureus2.plugins.ipfilter.*;
40 import org.gudy.azureus2.plugins.tracker.*;
41 import org.gudy.azureus2.plugins.tracker.web.*;
42 import org.gudy.azureus2.plugins.ui.*;
43 import org.gudy.azureus2.plugins.ui.config.*;
44 import org.gudy.azureus2.plugins.ui.model.*;
45 import org.json.simple.JSONObject;
46 
47 import com.aelitis.azureus.core.networkmanager.admin.NetworkAdmin;
48 import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminPropertyChangeListener;
49 import com.aelitis.azureus.core.pairing.PairedService;
50 import com.aelitis.azureus.core.pairing.PairedServiceRequestHandler;
51 import com.aelitis.azureus.core.pairing.PairingConnectionData;
52 import com.aelitis.azureus.core.pairing.PairingManager;
53 import com.aelitis.azureus.core.pairing.PairingManagerFactory;
54 import com.aelitis.azureus.core.pairing.PairingManagerListener;
55 import com.aelitis.azureus.core.proxy.AEProxyFactory;
56 import com.aelitis.azureus.plugins.upnp.UPnPMapping;
57 import com.aelitis.azureus.plugins.upnp.UPnPPlugin;
58 import com.aelitis.azureus.util.JSONUtils;
59 
60 public class
61 WebPlugin
62 	implements Plugin, TrackerWebPageGenerator
63 {
64 	public static final String	PR_ENABLE					= "Enable";						// Boolean
65 	public static final String	PR_DISABLABLE				= "Disablable";					// Boolean
66 	public static final String	PR_PORT						= "Port";						// Integer
67 	public static final String	PR_BIND_IP					= "Bind IP";					// String
68 	public static final String	PR_ROOT_RESOURCE			= "Root Resource";				// String
69 	public static final String 	PR_HOME_PAGE				= "Home Page";					// String
70 	public static final String	PR_ROOT_DIR					= "Root Dir";					// String
71 	public static final String	PR_ACCESS					= "Access";						// String
72 	public static final String	PR_LOG						= "DefaultLoggerChannel";		// LoggerChannel
73 	public static final String	PR_CONFIG_MODEL_PARAMS		= "DefaultConfigModelParams";	// String[] params to use when creating config model
74 	public static final String	PR_CONFIG_MODEL				= "DefaultConfigModel";			// BasicPluginConfigModel
75 	public static final String	PR_VIEW_MODEL				= "DefaultViewModel";			// BasicPluginViewModel
76 	public static final String	PR_HIDE_RESOURCE_CONFIG		= "DefaultHideResourceConfig";	// Boolean
77 	public static final String	PR_ENABLE_KEEP_ALIVE		= "DefaultEnableKeepAlive";		// Boolean
78 	public static final String	PR_PAIRING_SID				= "PairingSID";					// String
79 	public static final String	PR_NON_BLOCKING				= "NonBlocking";				// Boolean
80 	public static final String	PR_ENABLE_PAIRING			= "EnablePairing";				// Boolean
81 	public static final String	PR_ENABLE_I2P				= "EnableI2P";					// Boolean
82 	public static final String	PR_ENABLE_TOR				= "EnableTor";					// Boolean
83 	public static final String	PR_ENABLE_UPNP				= "EnableUPNP";					// Boolean
84 
85 	public static final String	PROPERTIES_MIGRATED		= "Properties Migrated";
86 	public static final String	CONFIG_MIGRATED			= "Config Migrated";
87 	public static final String	PAIRING_MIGRATED		= "Pairing Migrated";
88 	public static final String	PAIRING_SESSION_KEY		= "Pairing Session Key";
89 
90 	public static final String	CONFIG_PASSWORD_ENABLE			= "Password Enable";
91 	public static final boolean	CONFIG_PASSWORD_ENABLE_DEFAULT	= false;
92 
93 	public static final String	CONFIG_PAIRING_ENABLE			= "Pairing Enable";
94 	public static final boolean	CONFIG_PAIRING_ENABLE_DEFAULT	= true;
95 
96 	public static final String	CONFIG_PORT_OVERRIDE			= "Port Override";
97 
98 	public static final String	CONFIG_PAIRING_AUTO_AUTH			= "Pairing Auto Auth";
99 	public static final boolean	CONFIG_PAIRING_AUTO_AUTH_DEFAULT	= true;
100 
101 
102 	public static final String	CONFIG_ENABLE					= PR_ENABLE;
103 	public  			boolean	CONFIG_ENABLE_DEFAULT			= true;
104 
105 	public static final String	CONFIG_USER						= "User";
106 	public static final String	CONFIG_USER_DEFAULT				= "";
107 
108 	public static final String	CONFIG_PASSWORD					= "Password";
109 	public static final byte[]	CONFIG_PASSWORD_DEFAULT			= {};
110 
111 	public static final String 	CONFIG_PORT						= PR_PORT;
112 	public int			 		CONFIG_PORT_DEFAULT				= 8089;
113 
114 	public static final String 	CONFIG_BIND_IP					= PR_BIND_IP;
115 	public String		 		CONFIG_BIND_IP_DEFAULT			= "";
116 
117 	public static final String 	CONFIG_PROTOCOL					= "Protocol";
118 	public static final String 	CONFIG_PROTOCOL_DEFAULT			= "HTTP";
119 
120 	public static final String	CONFIG_UPNP_ENABLE				= "UPnP Enable";
121 	public 				boolean	CONFIG_UPNP_ENABLE_DEFAULT		= true;
122 
123 	public static final String 	CONFIG_HOME_PAGE				= PR_HOME_PAGE;
124 	public  		 String 	CONFIG_HOME_PAGE_DEFAULT		= "index.html";
125 
126 	public static final String 	CONFIG_ROOT_DIR					= PR_ROOT_DIR;
127 	public        		String 	CONFIG_ROOT_DIR_DEFAULT			= "";
128 
129 	public static final String 	CONFIG_ROOT_RESOURCE			= PR_ROOT_RESOURCE;
130 	public              String 	CONFIG_ROOT_RESOURCE_DEFAULT	= "";
131 
132 	public static final String 	CONFIG_MODE						= "Mode";
133 	public static final String 	CONFIG_MODE_FULL				= "full";
134 	public static final String 	CONFIG_MODE_DEFAULT				= CONFIG_MODE_FULL;
135 
136 	public static final String 	CONFIG_ACCESS					= PR_ACCESS;
137 	public        		String 	CONFIG_ACCESS_DEFAULT			= "all";
138 
139 	protected static final String	NL			= "\r\n";
140 
141 	protected static final String[]		welcome_pages = { "index.html", "index.htm", "index.php", "index.tmpl" };
142 	protected static File[]				welcome_files;
143 
144 	private static final AsyncDispatcher	network_dispatcher = new AsyncDispatcher( "webplugin:netdispatch", 5000 );
145 
146 	protected PluginInterface			plugin_interface;	// unfortunately this is accessed by webui - fix sometime
147 
148 	private LoggerChannel			log;
149 	private PluginConfig			plugin_config;
150 	private BasicPluginViewModel 	view_model;
151 	private BasicPluginConfigModel	config_model;
152 
153 	private String					p_sid;
154 
155 	private StringParameter			param_home;
156 	private StringParameter			param_rootdir;
157 	private StringParameter			param_rootres;
158 
159 	private IntParameter			param_port;
160 	private StringListParameter		param_protocol;
161 	private StringParameter			param_bind;
162 
163 	private StringParameter			param_access;
164 
165 	private InfoParameter			param_i2p_dest;
166 	private InfoParameter			param_tor_dest;
167 
168 	private BooleanParameter		p_upnp_enable;
169 
170 	private BooleanParameter		pw_enable;
171 	private StringParameter			p_user_name;
172 	private PasswordParameter		p_password;
173 
174 	private BooleanParameter		param_auto_auth;
175 	private IntParameter			param_port_or;
176 	private boolean					setting_auto_auth;
177 	private String					pairing_access_code;
178 	private String					pairing_session_code;
179 
180 	private boolean				plugin_enabled;
181 
182 	private boolean				na_intf_listener_added;
183 
184 	private String				home_page;
185 	private String				file_root;
186 	private String				resource_root;
187 
188 	private String				root_dir;
189 
190 	private boolean				ip_range_all	= false;
191 	private List<IPRange>		ip_ranges;
192 
193 	private TrackerWebContext			tracker_context;
194 	private UPnPMapping					upnp_mapping;
195 	private PairingManagerListener		pairing_listener;
196 
197 	private Properties	properties;
198 
199 	private static ThreadLocal<String>		tls	=
200 		new ThreadLocal<String>()
201 		{
202 			public String
203 			initialValue()
204 			{
205 				return( null );
206 			}
207 		};
208 
209 	private static final int	LOGOUT_GRACE_MILLIS	= 5*1000;
210 	private static final String	GRACE_PERIOD_MARKER	= "<grace_period>";
211 
212 	private Map<String,Long>	logout_timer 		= new HashMap<String, Long>();
213 
214 	private boolean	unloaded;
215 
216 	public
WebPlugin()217 	WebPlugin()
218 	{
219 		properties	= new Properties();
220 	}
221 
222 	public
WebPlugin( Properties defaults )223 	WebPlugin(
224 		Properties		defaults )
225 	{
226 		properties	= defaults;
227 	}
228 
229 	public void
initialize( PluginInterface _plugin_interface )230 	initialize(
231 		PluginInterface _plugin_interface )
232 
233 		throws PluginException
234 	{
235 		plugin_interface	= _plugin_interface;
236 
237 		plugin_config = plugin_interface.getPluginconfig();
238 
239 		Properties plugin_properties = plugin_interface.getPluginProperties();
240 
241 		if ( plugin_properties != null ){
242 
243 			Object o = plugin_properties.get( "plugin." + PR_ROOT_DIR.replaceAll( " ", "_" ));
244 
245 			if ( o instanceof String ){
246 
247 				properties.put( PR_ROOT_DIR, o );
248 			}
249 		}
250 
251 		Boolean	pr_enable = (Boolean)properties.get(PR_ENABLE);
252 
253 		if ( pr_enable != null ){
254 
255 			CONFIG_ENABLE_DEFAULT	= pr_enable.booleanValue();
256 		}
257 
258 		Integer	pr_port = (Integer)properties.get(PR_PORT);
259 
260 		if ( pr_port != null ){
261 
262 			CONFIG_PORT_DEFAULT	= pr_port.intValue();
263 		}
264 
265 		String	pr_bind_ip = (String)properties.get(PR_BIND_IP);
266 
267 		if ( pr_bind_ip != null ){
268 
269 			CONFIG_BIND_IP_DEFAULT	= pr_bind_ip.trim();
270 		}
271 
272 		String	pr_root_resource = (String)properties.get( PR_ROOT_RESOURCE );
273 
274 		if( pr_root_resource != null ){
275 
276 			CONFIG_ROOT_RESOURCE_DEFAULT	= pr_root_resource;
277 		}
278 
279 		String	pr_home_page = (String)properties.get( PR_HOME_PAGE );
280 
281 		if( pr_home_page != null ){
282 
283 			CONFIG_HOME_PAGE_DEFAULT		= pr_home_page;
284 		}
285 
286 		String	pr_root_dir = (String)properties.get( PR_ROOT_DIR );
287 
288 		if( pr_root_dir != null ){
289 
290 			CONFIG_ROOT_DIR_DEFAULT	= pr_root_dir;
291 		}
292 
293 		String	pr_access = (String)properties.get( PR_ACCESS );
294 
295 		if( pr_access != null ){
296 
297 			CONFIG_ACCESS_DEFAULT	= pr_access;
298 		}
299 
300 		Boolean	pr_enable_upnp = (Boolean)properties.get(PR_ENABLE_UPNP);
301 
302 		if ( pr_enable_upnp != null ){
303 
304 			CONFIG_UPNP_ENABLE_DEFAULT	= pr_enable_upnp.booleanValue();
305 		}
306 
307 		Boolean	pr_hide_resource_config = (Boolean)properties.get( PR_HIDE_RESOURCE_CONFIG );
308 
309 		log = (LoggerChannel)properties.get( PR_LOG );
310 
311 		if ( log == null ){
312 
313 			log = plugin_interface.getLogger().getChannel("WebPlugin");
314 		}
315 
316 		Boolean prop_pairing_enable = (Boolean)properties.get( PR_ENABLE_PAIRING );
317 
318 		if ( prop_pairing_enable == null || prop_pairing_enable ){
319 
320 				// default is based on sid availability
321 
322 			p_sid = (String)properties.get( PR_PAIRING_SID );
323 		}
324 
325 		UIManager	ui_manager = plugin_interface.getUIManager();
326 
327 		view_model = (BasicPluginViewModel)properties.get( PR_VIEW_MODEL );
328 
329 		if ( view_model == null ){
330 
331 			view_model = ui_manager.createBasicPluginViewModel( plugin_interface.getPluginName());
332 		}
333 
334 		String plugin_id = plugin_interface.getPluginID();
335 
336 		String sConfigSectionID = "plugins." + plugin_id;
337 
338 		view_model.setConfigSectionID(sConfigSectionID);
339 		view_model.getStatus().setText( "Running" );
340 		view_model.getActivity().setVisible( false );
341 		view_model.getProgress().setVisible( false );
342 
343 		log.addListener(
344 			new LoggerChannelListener()
345 			{
346 				public void
347 				messageLogged(
348 					int		type,
349 					String	message )
350 				{
351 					view_model.getLogArea().appendText( message+"\n");
352 				}
353 
354 				public void
355 				messageLogged(
356 					String		str,
357 					Throwable	error )
358 				{
359 					view_model.getLogArea().appendText( str + "\n" );
360 					view_model.getLogArea().appendText( error.toString() + "\n" );
361 				}
362 			});
363 
364 
365 		config_model = (BasicPluginConfigModel)properties.get( PR_CONFIG_MODEL );
366 
367 		if ( config_model == null ){
368 
369 			String[] cm_params = (String[])properties.get( PR_CONFIG_MODEL_PARAMS );
370 
371 			if ( cm_params == null || cm_params.length == 0 ){
372 
373 				config_model = ui_manager.createBasicPluginConfigModel(ConfigSection.SECTION_PLUGINS, sConfigSectionID);
374 
375 			}else if ( cm_params.length == 1 ){
376 
377 				config_model = ui_manager.createBasicPluginConfigModel( cm_params[0] );
378 
379 			}else{
380 
381 				config_model = ui_manager.createBasicPluginConfigModel( cm_params[0], cm_params[1] );
382 			}
383 		}
384 
385 		boolean	save_needed = false;
386 
387 		if ( !plugin_config.getPluginBooleanParameter( CONFIG_MIGRATED, false )){
388 
389 			plugin_config.setPluginParameter( CONFIG_MIGRATED, true );
390 
391 			save_needed	= true;
392 
393 			plugin_config.setPluginParameter(
394 					CONFIG_PASSWORD_ENABLE,
395 					plugin_config.getBooleanParameter(
396 							"Tracker Password Enable Web", CONFIG_PASSWORD_ENABLE_DEFAULT ));
397 
398 			plugin_config.setPluginParameter(
399 					CONFIG_USER,
400 					plugin_config.getStringParameter(
401 							"Tracker Username", CONFIG_USER_DEFAULT ));
402 
403 			plugin_config.setPluginParameter(
404 					CONFIG_PASSWORD,
405 					plugin_config.getByteParameter(
406 							"Tracker Password", CONFIG_PASSWORD_DEFAULT ));
407 
408 		}
409 
410 		if ( !plugin_config.getPluginBooleanParameter( PROPERTIES_MIGRATED, false )){
411 
412 			plugin_config.setPluginParameter( PROPERTIES_MIGRATED, true );
413 
414 			Properties	props = plugin_interface.getPluginProperties();
415 
416 				// make sure we've got an old properties file too
417 
418 			if ( props.getProperty( "port", "" ).length() > 0 ){
419 
420 				save_needed = true;
421 
422 				String	prop_port		= props.getProperty( "port",			""+CONFIG_PORT_DEFAULT );
423 				String	prop_protocol	= props.getProperty( "protocol", 		CONFIG_PROTOCOL_DEFAULT );
424 				String	prop_home		= props.getProperty( "homepage", 		CONFIG_HOME_PAGE_DEFAULT );
425 				String	prop_rootdir	= props.getProperty( "rootdir", 		CONFIG_ROOT_DIR_DEFAULT );
426 				String	prop_rootres	= props.getProperty( "rootresource", 	CONFIG_ROOT_RESOURCE_DEFAULT );
427 				String	prop_mode		= props.getProperty( "mode", 			CONFIG_MODE_DEFAULT );
428 				String	prop_access		= props.getProperty( "access", 			CONFIG_ACCESS_DEFAULT );
429 
430 				int	prop_port_int = CONFIG_PORT_DEFAULT;
431 
432 				try{
433 					prop_port_int	= Integer.parseInt( prop_port );
434 
435 				}catch( Throwable e ){
436 				}
437 
438 				plugin_config.setPluginParameter(CONFIG_PORT, prop_port_int );
439 				plugin_config.setPluginParameter(CONFIG_PROTOCOL, prop_protocol );
440 				plugin_config.setPluginParameter(CONFIG_HOME_PAGE, prop_home );
441 				plugin_config.setPluginParameter(CONFIG_ROOT_DIR, prop_rootdir );
442 				plugin_config.setPluginParameter(CONFIG_ROOT_RESOURCE, prop_rootres );
443 				plugin_config.setPluginParameter(CONFIG_MODE, prop_mode );
444 				plugin_config.setPluginParameter(CONFIG_ACCESS, prop_access );
445 
446 				File	props_file = new File( plugin_interface.getPluginDirectoryName(), "plugin.properties" );
447 
448 				PrintWriter pw = null;
449 
450 				try{
451 					File	backup = new File( plugin_interface.getPluginDirectoryName(), "plugin.properties.bak" );
452 
453 					props_file.renameTo( backup );
454 
455 					pw = new PrintWriter( new FileWriter( props_file ));
456 
457 					pw.println( "plugin.class=" + props.getProperty( "plugin.class" ));
458 					pw.println( "plugin.name=" + props.getProperty( "plugin.name" ));
459 					pw.println( "plugin.version=" + props.getProperty( "plugin.version" ));
460 					pw.println( "plugin.id=" + props.getProperty( "plugin.id" ));
461 					pw.println( "" );
462 					pw.println( "# configuration has been migrated to plugin config - see view->config->plugins" );
463 					pw.println( "# in the SWT user interface" );
464 
465 					log.logAlert( 	LoggerChannel.LT_INFORMATION,
466 							plugin_interface.getPluginName() + " - plugin.properties settings migrated to plugin configuration." );
467 
468 				}catch( Throwable  e ){
469 
470 					Debug.printStackTrace( e );
471 
472 					log.logAlert( 	LoggerChannel.LT_ERROR,
473 									plugin_interface.getPluginName() + " - plugin.properties settings migration failed." );
474 
475 				}finally{
476 
477 					if ( pw != null ){
478 
479 						pw.close();
480 					}
481 				}
482 			}
483 		}
484 
485 		if ( save_needed ){
486 
487 			plugin_config.save();
488 		}
489 
490 		Boolean	disablable = (Boolean)properties.get( PR_DISABLABLE );
491 
492 		final BooleanParameter	param_enable;
493 
494 		if ( disablable != null && disablable ){
495 
496 			param_enable =
497 				config_model.addBooleanParameter2( CONFIG_ENABLE, "webui.enable", CONFIG_ENABLE_DEFAULT );
498 
499 			plugin_enabled = param_enable.getValue();
500 
501 		}else{
502 			param_enable 	= null;
503 
504 			plugin_enabled 	= true;
505 		}
506 
507 		initStage(1);
508 
509 			// connection group
510 
511 		param_port = config_model.addIntParameter2(		CONFIG_PORT, "webui.port", CONFIG_PORT_DEFAULT );
512 
513 		param_port.setGenerateIntermediateEvents( false );
514 
515 		param_bind = config_model.addStringParameter2(	CONFIG_BIND_IP, "webui.bindip", CONFIG_BIND_IP_DEFAULT );
516 
517 		param_bind.setGenerateIntermediateEvents( false );
518 
519 		param_protocol =
520 			config_model.addStringListParameter2(
521 					CONFIG_PROTOCOL, "webui.protocol", new String[]{ "http", "https" }, CONFIG_PROTOCOL_DEFAULT );
522 
523 		param_protocol.setGenerateIntermediateEvents( false );
524 
525 		ParameterListener update_server_listener =
526 			new ParameterListener()
527 			{
528 				public void
529 				parameterChanged(
530 					Parameter param )
531 				{
532 					setupServer();
533 				}
534 			};
535 
536 		param_port.addListener( update_server_listener );
537 		param_bind.addListener( update_server_listener );
538 		param_protocol.addListener( update_server_listener );
539 
540 		param_i2p_dest = config_model.addInfoParameter2( "webui.i2p_dest", "" );
541 		param_i2p_dest.setVisible( false );
542 
543 		param_tor_dest = config_model.addInfoParameter2( "webui.tor_dest", "" );
544 		param_tor_dest.setVisible( false );
545 
546 		if ( param_enable != null ){
547 			COConfigurationManager.registerExportedParameter( plugin_id + ".enable", param_enable.getConfigKeyName());
548 		}
549 		COConfigurationManager.registerExportedParameter( plugin_id + ".port", param_port.getConfigKeyName());
550 		COConfigurationManager.registerExportedParameter( plugin_id + ".protocol", param_protocol.getConfigKeyName());
551 
552 		p_upnp_enable =
553 			config_model.addBooleanParameter2(
554 							CONFIG_UPNP_ENABLE,
555 							"webui.upnpenable",
556 							CONFIG_UPNP_ENABLE_DEFAULT );
557 
558 		p_upnp_enable.addListener(
559 			new ParameterListener()
560 			{
561 				public void
562 				parameterChanged(
563 					Parameter param )
564 				{
565 					setupUPnP();
566 				}
567 			});
568 
569 		plugin_interface.addListener(
570 				new PluginListener()
571 				{
572 					public void
573 					initializationComplete()
574 					{
575 						setupUPnP();
576 					}
577 
578 					public void
579 					closedownInitiated()
580 					{
581 					}
582 
583 					public void
584 					closedownComplete()
585 					{
586 					}
587 				});
588 
589 
590 		final LabelParameter		pairing_info;
591 		final BooleanParameter		pairing_enable;
592 		final HyperlinkParameter	pairing_test;
593 		final HyperlinkParameter	connection_test;
594 
595 		if ( p_sid != null ){
596 
597 			final PairingManager pm = PairingManagerFactory.getSingleton();
598 
599 			pairing_info = config_model.addLabelParameter2( "webui.pairing.info." + (pm.isEnabled()?"y":"n"));
600 
601 			pairing_enable = config_model.addBooleanParameter2( CONFIG_PAIRING_ENABLE, "webui.pairingenable", CONFIG_PAIRING_ENABLE_DEFAULT );
602 
603 			if ( !plugin_config.getPluginBooleanParameter( PAIRING_MIGRATED, false )){
604 
605 					// if they already have a password, don't override it by setting auto-auth
606 
607 				boolean	has_pw_enabled = plugin_config.getPluginBooleanParameter( CONFIG_PASSWORD_ENABLE, CONFIG_PASSWORD_ENABLE_DEFAULT );
608 
609 				if ( has_pw_enabled ){
610 
611 					plugin_config.setPluginParameter( CONFIG_PAIRING_AUTO_AUTH, false );
612 				}
613 
614 				plugin_config.setPluginParameter( PAIRING_MIGRATED, true );
615 			}
616 
617 			param_port_or	=  config_model.addIntParameter2( CONFIG_PORT_OVERRIDE, "webui.port.override", 0 );
618 
619 			param_auto_auth = config_model.addBooleanParameter2( CONFIG_PAIRING_AUTO_AUTH, "webui.pairing.autoauth", CONFIG_PAIRING_AUTO_AUTH_DEFAULT );
620 
621 			param_auto_auth.addListener(
622 				new ParameterListener()
623 				{
624 					public void
625 					parameterChanged(
626 						Parameter param )
627 					{
628 						if ( pairing_enable.getValue() && pm.isEnabled()){
629 
630 							setupAutoAuth();
631 
632 						}else{
633 
634 							setupSessionCode( null );
635 						}
636 					}
637 				});
638 
639 			connection_test = config_model.addHyperlinkParameter2( "webui.connectiontest", getConnectionTestURL( p_sid ));
640 
641 			pairing_test = config_model.addHyperlinkParameter2( "webui.pairingtest", "http://remote.vuze.com/?sid=" + p_sid );
642 
643 				// listeners setup later as they depend on userame params etc
644 
645 			String sid_key =  "Plugin." + plugin_id + ".pairing.sid";
646 
647 			COConfigurationManager.setStringDefault( sid_key, p_sid );
648 
649 			COConfigurationManager.registerExportedParameter( plugin_id + ".pairing.sid", sid_key);
650 			COConfigurationManager.registerExportedParameter( plugin_id + ".pairing.enable", pairing_enable.getConfigKeyName());
651 			COConfigurationManager.registerExportedParameter( plugin_id + ".pairing.auto_auth", param_auto_auth.getConfigKeyName());
652 
653 		}else{
654 			pairing_info	= null;
655 			pairing_enable 	= null;
656 			param_auto_auth	= null;
657 			param_port_or	= null;
658 			pairing_test	= null;
659 			connection_test	= null;
660 		}
661 
662 		config_model.createGroup(
663 				"ConfigView.section.Pairing",
664 				new Parameter[]{
665 					pairing_info, pairing_enable, param_port_or, param_auto_auth, connection_test, pairing_test,
666 				});
667 
668 		config_model.createGroup(
669 			"ConfigView.section.server",
670 			new Parameter[]{
671 				param_port, param_bind, param_protocol, param_i2p_dest, param_tor_dest, p_upnp_enable,
672 			});
673 
674 		param_home 		= config_model.addStringParameter2(	CONFIG_HOME_PAGE, "webui.homepage", CONFIG_HOME_PAGE_DEFAULT );
675 		param_rootdir 	= config_model.addStringParameter2(	CONFIG_ROOT_DIR, "webui.rootdir", CONFIG_ROOT_DIR_DEFAULT );
676 		param_rootres	= config_model.addStringParameter2(	CONFIG_ROOT_RESOURCE, "webui.rootres", CONFIG_ROOT_RESOURCE_DEFAULT );
677 
678 		if ( pr_hide_resource_config != null && pr_hide_resource_config.booleanValue()){
679 
680 			param_home.setVisible( false );
681 			param_rootdir.setVisible( false );
682 			param_rootres.setVisible( false );
683 
684 		}else{
685 
686 			ParameterListener update_resources_listener =
687 				new ParameterListener()
688 				{
689 					public void
690 					parameterChanged(
691 						Parameter param )
692 					{
693 						setupResources();
694 					}
695 				};
696 
697 			param_home.addListener( update_resources_listener );
698 			param_rootdir.addListener( update_resources_listener );
699 			param_rootres.addListener( update_resources_listener );
700 		}
701 
702 			// access group
703 
704 		LabelParameter a_label1 = config_model.addLabelParameter2( "webui.mode.info" );
705 		StringListParameter param_mode =
706 			config_model.addStringListParameter2(
707 					CONFIG_MODE, "webui.mode", new String[]{ "full", "view" }, CONFIG_MODE_DEFAULT );
708 
709 
710 		LabelParameter a_label2 = config_model.addLabelParameter2( "webui.access.info" );
711 
712 		param_access	= config_model.addStringParameter2(	CONFIG_ACCESS, "webui.access", CONFIG_ACCESS_DEFAULT );
713 
714 		param_access.addListener(
715 			new ParameterListener()
716 			{
717 				public void
718 				parameterChanged(
719 					Parameter param )
720 				{
721 					setupAccess();
722 				}
723 			});
724 
725 		pw_enable =
726 			config_model.addBooleanParameter2(
727 							CONFIG_PASSWORD_ENABLE,
728 							"webui.passwordenable",
729 							CONFIG_PASSWORD_ENABLE_DEFAULT );
730 
731 		p_user_name =
732 			config_model.addStringParameter2(
733 							CONFIG_USER,
734 							"webui.user",
735 							CONFIG_USER_DEFAULT );
736 
737 		p_password =
738 			config_model.addPasswordParameter2(
739 							CONFIG_PASSWORD,
740 							"webui.password",
741 							PasswordParameter.ET_SHA1,
742 							CONFIG_PASSWORD_DEFAULT );
743 
744 		pw_enable.addEnabledOnSelection( p_user_name );
745 		pw_enable.addEnabledOnSelection( p_password );
746 
747 		ParameterListener auth_change_listener =
748 			new ParameterListener()
749 			{
750 				public void
751 				parameterChanged(
752 					Parameter param )
753 				{
754 					if ( param_auto_auth != null ){
755 
756 						if ( !setting_auto_auth ){
757 
758 							log( "Disabling pairing auto-authentication as overridden by user" );
759 
760 							param_auto_auth.setValue( false );
761 						}
762 					}
763 
764 					if ( param == p_user_name || param == p_password ){
765 
766 						setupSessionCode( null );
767 					}
768 				}
769 			};
770 
771 		p_user_name.addListener( auth_change_listener );
772 		p_password.addListener( auth_change_listener );
773 		pw_enable.addListener( auth_change_listener );
774 
775 		config_model.createGroup(
776 			"webui.group.access",
777 			new Parameter[]{
778 				a_label1, param_mode, a_label2, param_access,
779 				pw_enable, p_user_name, p_password,
780 			});
781 
782 		if ( p_sid != null ){
783 
784 			final PairingManager pm = PairingManagerFactory.getSingleton();
785 
786 			pairing_enable.addListener(
787 					new ParameterListener()
788 					{
789 						public void
790 						parameterChanged(
791 							Parameter param )
792 						{
793 							boolean enabled = pairing_enable.getValue();
794 
795 							param_auto_auth.setEnabled( pm.isEnabled() && enabled );
796 							param_port_or.setEnabled( pm.isEnabled() && enabled );
797 
798 							boolean test_ok = pm.isEnabled() && pairing_enable.getValue() && pm.peekAccessCode() != null && !pm.hasActionOutstanding();
799 
800 							pairing_test.setEnabled( test_ok );
801 							connection_test.setEnabled( test_ok );
802 
803 							setupPairing( p_sid, enabled );
804 						}
805 					});
806 
807 			pairing_listener =
808 				new PairingManagerListener()
809 				{
810 					public void
811 					somethingChanged(
812 						PairingManager pm )
813 					{
814 						pairing_info.setLabelKey( "webui.pairing.info." + (pm.isEnabled()?"y":"n"));
815 
816 						if ( plugin_enabled ){
817 
818 							pairing_enable.setEnabled( pm.isEnabled());
819 
820 							param_auto_auth.setEnabled( pm.isEnabled() && pairing_enable.getValue() );
821 							param_port_or.setEnabled( pm.isEnabled() && pairing_enable.getValue() );
822 
823 							boolean test_ok = pm.isEnabled() && pairing_enable.getValue() && pm.peekAccessCode() != null && !pm.hasActionOutstanding();
824 
825 							pairing_test.setEnabled( test_ok );
826 							connection_test.setEnabled( test_ok );
827 						}
828 
829 						connection_test.setHyperlink( getConnectionTestURL( p_sid ));
830 
831 						setupPairing( p_sid, pairing_enable.getValue());
832 					}
833 				};
834 
835 			pairing_listener.somethingChanged( pm );
836 
837 			pm.addListener( pairing_listener );
838 
839 			setupPairing( p_sid, pairing_enable.getValue());
840 
841 			ParameterListener update_pairing_listener =
842 				new ParameterListener()
843 				{
844 					public void
845 					parameterChanged(
846 						Parameter param )
847 					{
848 						updatePairing( p_sid );
849 
850 						setupUPnP();
851 					}
852 				};
853 
854 			param_port.addListener( update_pairing_listener );
855 
856 			param_port_or.addListener( update_pairing_listener );
857 
858 			param_protocol.addListener( update_pairing_listener );
859 
860 			/*
861 			config_model.addActionParameter2( "test", "test" ).addListener(
862 				new ParameterListener()
863 				{
864 					public void
865 					parameterChanged(
866 						Parameter param )
867 					{
868 						try{
869 							pm.testService(
870 								p_sid,
871 								new PairingTestListener()
872 								{
873 									public void
874 									testStarted(
875 										PairingTest test )
876 									{
877 										System.out.println( "Test starts" );
878 									}
879 
880 									public void
881 									testComplete(
882 										PairingTest test)
883 									{
884 										System.out.println( "Test complete: " + test.getOutcome() + "/" + test.getErrorMessage());
885 									}
886 								});
887 						}catch( Throwable e ){
888 
889 							Debug.out( e );
890 						}
891 					}
892 				});
893 			*/
894 		}
895 
896 		if ( param_enable != null ){
897 
898 			final List<Parameter> changed_params = new ArrayList<Parameter>();
899 
900 			if ( !plugin_enabled){
901 
902 				Parameter[] params = config_model.getParameters();
903 
904 				for ( Parameter param: params ){
905 
906 					if ( param == param_enable ){
907 
908 						continue;
909 					}
910 
911 					if ( param.isEnabled()){
912 
913 						changed_params.add( param );
914 
915 						param.setEnabled( false );
916 					}
917 				}
918 			}
919 
920 			param_enable.addListener(
921 				new ParameterListener()
922 				{
923 					public void
924 					parameterChanged(
925 						Parameter e_p )
926 					{
927 							// this doesn't quite work as tne enabler/disabler parameter logic is implemented
928 							// badly and only toggles the UI component, not the enabled state of the
929 							// underlying parameter. grr. better than nothing though
930 
931 						plugin_enabled = ((BooleanParameter)e_p).getValue();
932 
933 						if ( plugin_enabled ){
934 
935 							for ( Parameter p: changed_params ){
936 
937 								p.setEnabled( true );
938 							}
939 						}else{
940 
941 							changed_params.clear();
942 
943 							Parameter[] params = config_model.getParameters();
944 
945 							for ( Parameter param: params ){
946 
947 								if ( param == e_p ){
948 
949 									continue;
950 								}
951 
952 								if ( param.isEnabled()){
953 
954 									changed_params.add( param );
955 
956 									param.setEnabled( false );
957 								}
958 							}
959 						}
960 
961 						setupServer();
962 
963 						setupUPnP();
964 
965 						if ( p_sid != null ){
966 
967 							setupPairing( p_sid, pairing_enable.getValue());
968 						}
969 					}
970 				});
971 		}
972 
973 			// end config
974 
975 		setupResources();
976 
977 		setupAccess();
978 
979 		setupServer();
980 	}
981 
982 	protected void
initStage( int num )983 	initStage(
984 		int	num )
985 	{
986 	}
987 
988 	private String
getConnectionTestURL( String sid )989 	getConnectionTestURL(
990 		String		sid )
991 	{
992 		String res = "http://pair.vuze.com/pairing/web/test?sid=" + sid;
993 
994 		PairingManager pm = PairingManagerFactory.getSingleton();
995 
996 		if ( pm.isEnabled()){
997 
998 			String ac = pm.peekAccessCode();
999 
1000 			if ( ac != null ){
1001 
1002 				res += "&ac=" + ac;
1003 			}
1004 		}
1005 
1006 		return( res );
1007 	}
1008 
1009 	protected boolean
isPluginEnabled()1010 	isPluginEnabled()
1011 	{
1012 		return( plugin_enabled );
1013 	}
1014 
1015 	protected void
unloadPlugin()1016 	unloadPlugin()
1017 	{
1018 		if ( view_model != null ){
1019 
1020 			view_model.destroy();
1021 
1022 			view_model = null;
1023 		}
1024 
1025 		if ( config_model != null ){
1026 
1027 			config_model.destroy();
1028 
1029 			config_model = null;
1030 		}
1031 
1032 		if ( tracker_context != null ){
1033 
1034 			tracker_context.destroy();
1035 
1036 			tracker_context = null;
1037 		}
1038 
1039 		if ( upnp_mapping != null ){
1040 
1041 			upnp_mapping.destroy();
1042 
1043 			upnp_mapping = null;
1044 		}
1045 
1046 		if ( pairing_listener != null ){
1047 
1048 			PairingManager pm = PairingManagerFactory.getSingleton();
1049 
1050 			pm.removeListener( pairing_listener );
1051 
1052 			pairing_listener = null;
1053 		}
1054 
1055 		unloaded = true;
1056 	}
1057 
1058 	private void
setupResources()1059 	setupResources()
1060 	{
1061 		home_page = param_home.getValue().trim();
1062 
1063 		if ( home_page.length() == 0 ){
1064 
1065 			home_page = null;
1066 
1067 		}else if ( !home_page.startsWith("/" )){
1068 
1069 			home_page = "/" + home_page;
1070 		}
1071 
1072 		resource_root = param_rootres.getValue().trim();
1073 
1074 		if ( resource_root.length() == 0 ){
1075 
1076 			resource_root = null;
1077 
1078 		}else if ( resource_root.startsWith("/" )){
1079 
1080 			resource_root = resource_root.substring(1);
1081 		}
1082 
1083 		root_dir	= param_rootdir.getValue().trim();
1084 
1085 		if ( root_dir.length() == 0 ){
1086 
1087 			file_root = plugin_interface.getPluginDirectoryName();
1088 
1089 			if ( file_root == null ){
1090 
1091 				file_root = SystemProperties.getUserPath() + "web";
1092 			}
1093 		}else{
1094 
1095 				// absolute or relative
1096 
1097 			if ( root_dir.startsWith(File.separator) || root_dir.indexOf(":") != -1 ){
1098 
1099 				file_root = root_dir;
1100 
1101 			}else{
1102 
1103 				if ( File.separatorChar != '/' && root_dir.contains( "/" )){
1104 
1105 					root_dir = root_dir.replace( '/', File.separatorChar );
1106 				}
1107 
1108 					// try relative to plugin dir
1109 
1110 				file_root = plugin_interface.getPluginDirectoryName();
1111 
1112 				if ( file_root != null ){
1113 
1114 					file_root = file_root + File.separator + root_dir;
1115 
1116 					if ( !new File(file_root).exists()){
1117 
1118 						// try relative to plugin classpath
1119 						try {
1120 							String pluginClass = plugin_interface.getPluginProperties().getProperty(
1121 									"plugin.class");
1122 							file_root = new File(
1123 									Class.forName(
1124 											pluginClass).getProtectionDomain().getCodeSource().getLocation().getPath(),
1125 									root_dir).getAbsolutePath();
1126 							if (!new File(file_root).exists()) {
1127 
1128 								file_root = null;
1129 							}
1130 						} catch (Throwable e) {
1131 						}
1132 
1133 					}
1134 				}
1135 
1136 				if ( file_root == null ){
1137 
1138 					file_root = SystemProperties.getUserPath() + "web" + File.separator + root_dir;
1139 				}
1140 			}
1141 		}
1142 
1143 		File	f_root = new File( file_root );
1144 
1145 		if ( !f_root.exists()){
1146 
1147 			String	error = "WebPlugin: root dir '" + file_root + "' doesn't exist";
1148 
1149 			log.log( LoggerChannel.LT_ERROR, error );
1150 
1151 		}else if ( !f_root.isDirectory()){
1152 
1153 			String	error = "WebPlugin: root dir '" + file_root + "' isn't a directory";
1154 
1155 			log.log( LoggerChannel.LT_ERROR, error );
1156 		}
1157 
1158 		welcome_files = new File[welcome_pages.length];
1159 
1160 		for (int i=0;i<welcome_pages.length;i++){
1161 
1162 			welcome_files[i] = new File( file_root + File.separator + welcome_pages[i] );
1163 		}
1164 	}
1165 
1166 	private void
setupAccess()1167 	setupAccess()
1168 	{
1169 		String	access_str = param_access.getValue().trim();
1170 
1171 		String ip_ranges_str = "";
1172 
1173 		ip_ranges 		= null;
1174 		ip_range_all	= false;
1175 
1176 		if ( access_str.length() > 7 && Character.isDigit(access_str.charAt(0))){
1177 
1178 			String[] ranges = access_str.replace( ';', ',' ).split( "," );
1179 
1180 			ip_ranges = new ArrayList<IPRange>();
1181 
1182 			for ( String range: ranges ){
1183 
1184 				range = range.trim();
1185 
1186 				if ( range.length() > 7 ){
1187 
1188 					IPRange ip_range	= plugin_interface.getIPFilter().createRange(true);
1189 
1190 					int	sep = range.indexOf("-");
1191 
1192 					if ( sep == -1 ){
1193 
1194 						ip_range.setStartIP( range );
1195 
1196 						ip_range.setEndIP( range );
1197 
1198 					}else{
1199 
1200 						ip_range.setStartIP( range.substring(0,sep).trim());
1201 
1202 						ip_range.setEndIP( range.substring( sep+1 ).trim());
1203 					}
1204 
1205 					ip_range.checkValid();
1206 
1207 					if (!ip_range.isValid()){
1208 
1209 						log.log( LoggerChannel.LT_ERROR, "Access parameter '" + range + "' is invalid" );
1210 
1211 					}else{
1212 
1213 						ip_ranges.add( ip_range );
1214 
1215 						ip_ranges_str += (ip_ranges_str.length()==0?"":", ") + ip_range.getStartIP() + " - " + ip_range.getEndIP();
1216 					}
1217 				}
1218 			}
1219 
1220 			if ( ip_ranges.size() == 0 ){
1221 
1222 				ip_ranges = null;
1223 			}
1224 		}else{
1225 
1226 			if ( access_str.equalsIgnoreCase( "all" ) || access_str.length() == 0 ){
1227 
1228 				ip_range_all	= true;
1229 			}
1230 		}
1231 
1232 		log.log( 	LoggerChannel.LT_INFORMATION,
1233 				"Acceptable IP range = " +
1234 					( ip_ranges==null?
1235 						(ip_range_all?"all":"local"):
1236 						(ip_ranges_str)));
1237 	}
1238 
1239 	protected void
setupServer()1240 	setupServer()
1241 	{
1242 		try{
1243 			if ( !plugin_enabled ){
1244 
1245 				if ( tracker_context != null ){
1246 
1247 					tracker_context.destroy();
1248 
1249 					tracker_context = null;
1250 				}
1251 
1252 				return;
1253 			}
1254 
1255 			final int port	= param_port.getValue();
1256 
1257 			String protocol_str = param_protocol.getValue().trim();
1258 
1259 			String bind_str = param_bind.getValue().trim();
1260 
1261 			InetAddress	bind_ip = null;
1262 
1263 			if ( bind_str.length() > 0 ){
1264 
1265 				try{
1266 					bind_ip = InetAddress.getByName( bind_str );
1267 
1268 				}catch( Throwable  e ){
1269 				}
1270 
1271 				if ( bind_ip == null ){
1272 
1273 						// might be an interface name, see if we can resolve it
1274 
1275 					final NetworkAdmin na = NetworkAdmin.getSingleton();
1276 
1277 					InetAddress[] addresses = na.resolveBindAddresses( bind_str );
1278 
1279 					if ( addresses.length > 0 ){
1280 
1281 						bind_ip = addresses[0];
1282 
1283 						if ( !na_intf_listener_added ){
1284 
1285 							na_intf_listener_added = true;
1286 
1287 							na.addPropertyChangeListener(
1288 								new NetworkAdminPropertyChangeListener()
1289 								{
1290 									public void
1291 									propertyChanged(
1292 										String property)
1293 									{
1294 										if ( unloaded ){
1295 
1296 											na.removePropertyChangeListener( this );
1297 
1298 										}else{
1299 
1300 											if ( property == NetworkAdmin.PR_NETWORK_INTERFACES ){
1301 
1302 												new AEThread2( "setupserver" )
1303 												{
1304 													public void
1305 													run()
1306 													{
1307 														setupServer();
1308 													}
1309 												}.start();
1310 											}
1311 										}
1312 									}
1313 								});
1314 						}
1315 					}
1316 				}
1317 
1318 				if ( bind_ip == null ){
1319 
1320 					log.log( LoggerChannel.LT_ERROR, "Bind IP parameter '" + bind_str + "' is invalid" );
1321 				}
1322 			}
1323 
1324 			if ( tracker_context != null ){
1325 
1326 				URL	url = tracker_context.getURLs()[0];
1327 
1328 				String		existing_protocol 	= url.getProtocol();
1329 				int			existing_port		= url.getPort()==-1?url.getDefaultPort():url.getPort();
1330 				InetAddress existing_bind_ip 	= tracker_context.getBindIP();
1331 
1332 				if ( 	existing_port == port &&
1333 						existing_protocol.equalsIgnoreCase( protocol_str ) &&
1334 						sameAddress( bind_ip, existing_bind_ip )){
1335 
1336 					return;
1337 				}
1338 
1339 				tracker_context.destroy();
1340 
1341 				tracker_context = null;
1342 			}
1343 
1344 
1345 
1346 			int	protocol = protocol_str.equalsIgnoreCase( "HTTP")?Tracker.PR_HTTP:Tracker.PR_HTTPS;
1347 
1348 			Map<String,Object>		tc_properties = new HashMap<String, Object>();
1349 
1350 			Boolean prop_non_blocking = (Boolean)properties.get( PR_NON_BLOCKING );
1351 
1352 			if ( prop_non_blocking != null && prop_non_blocking ){
1353 
1354 				tc_properties.put( Tracker.PR_NON_BLOCKING, true );
1355 			}
1356 
1357 			log.log( 	LoggerChannel.LT_INFORMATION,
1358 						"Server initialisation: port=" + port +
1359 						(bind_ip == null?"":(", bind=" + bind_str + "->" + bind_ip + ")")) +
1360 						", protocol=" + protocol_str +
1361 						(root_dir.length()==0?"":(", root=" + root_dir )) +
1362 						(properties.size()==0?"":(", props=" + properties )));
1363 
1364 			tracker_context =
1365 				plugin_interface.getTracker().createWebContext(
1366 						Constants.APP_NAME + " - " + plugin_interface.getPluginName(),
1367 						port, protocol, bind_ip, tc_properties );
1368 
1369 			Boolean prop_enable_i2p = (Boolean)properties.get( PR_ENABLE_I2P );
1370 
1371 			if ( prop_enable_i2p == null || prop_enable_i2p ){
1372 
1373 				network_dispatcher.dispatch(
1374 					new AERunnable()
1375 					{
1376 						public void
1377 						runSupport()
1378 						{
1379 							Map<String,Object>	options = new HashMap<String, Object>();
1380 
1381 							options.put( AEProxyFactory.SP_PORT, port );
1382 
1383 							Map<String,Object> reply =
1384 									AEProxyFactory.getPluginServerProxy(
1385 										plugin_interface.getPluginName(),
1386 										AENetworkClassifier.AT_I2P,
1387 										plugin_interface.getPluginID(),
1388 										options );
1389 
1390 							if ( reply != null ){
1391 
1392 								param_i2p_dest.setVisible( true );
1393 
1394 								String host = (String)reply.get( "host" );
1395 
1396 								if ( !param_i2p_dest.getValue().equals( host )){
1397 
1398 									param_i2p_dest.setValue( host );
1399 
1400 									if ( p_sid != null ){
1401 
1402 										updatePairing( p_sid );
1403 									}
1404 								}
1405 							}
1406 						}
1407 					});
1408 			}
1409 
1410 			Boolean prop_enable_tor = (Boolean)properties.get( PR_ENABLE_TOR );
1411 
1412 			if ( prop_enable_tor == null || prop_enable_tor ){
1413 
1414 				network_dispatcher.dispatch(
1415 					new AERunnable()
1416 					{
1417 						public void
1418 						runSupport()
1419 						{
1420 							Map<String,Object>	options = new HashMap<String, Object>();
1421 
1422 							options.put( AEProxyFactory.SP_PORT, port );
1423 
1424 							Map<String,Object> reply =
1425 									AEProxyFactory.getPluginServerProxy(
1426 										plugin_interface.getPluginName(),
1427 										AENetworkClassifier.AT_TOR,
1428 										plugin_interface.getPluginID(),
1429 										options );
1430 
1431 							if ( reply != null ){
1432 
1433 								param_tor_dest.setVisible( true );
1434 
1435 								String host = (String)reply.get( "host" );
1436 
1437 								if ( !param_tor_dest.getValue().equals( host )){
1438 
1439 									param_tor_dest.setValue( host );
1440 
1441 									if ( p_sid != null ){
1442 
1443 										updatePairing( p_sid );
1444 									}
1445 								}
1446 							}
1447 						}
1448 					});
1449 			}
1450 
1451 
1452 			Boolean	pr_enable_keep_alive = (Boolean)properties.get( PR_ENABLE_KEEP_ALIVE );
1453 
1454 			if ( pr_enable_keep_alive != null && pr_enable_keep_alive ){
1455 
1456 				tracker_context.setEnableKeepAlive( true );
1457 			}
1458 
1459 			tracker_context.addPageGenerator( this );
1460 
1461 			tracker_context.addAuthenticationListener(
1462 				new TrackerAuthenticationAdapter()
1463 				{
1464 					private String	last_pw		= "";
1465 					private byte[]	last_hash	= {};
1466 
1467 					private final int DELAY = 10*1000;
1468 
1469 					private Map<String,Object[]>	fail_map = new HashMap<String, Object[]>();
1470 
1471 					public boolean
1472 					authenticate(
1473 						String		headers,
1474 						URL			resource,
1475 						String		user,
1476 						String		pw )
1477 					{
1478 						//System.out.println( resource + ": " + user + "/" + pw );
1479 
1480 						long	now = SystemTime.getMonotonousTime();
1481 
1482 						String	client_address = getHeaderField( headers, "X-Real-IP" );
1483 
1484 						if ( client_address == null ){
1485 
1486 							client_address = "<unknown>";
1487 						}
1488 
1489 						synchronized( logout_timer ){
1490 
1491 							Long logout_time = logout_timer.get( client_address );
1492 
1493 							if ( logout_time != null && now - logout_time <= LOGOUT_GRACE_MILLIS ){
1494 
1495 								tls.set( GRACE_PERIOD_MARKER );
1496 
1497 								return( true );
1498 							}
1499 						}
1500 
1501 						boolean	result = authenticateSupport( headers, resource, user, pw );
1502 
1503 						if ( !result ){
1504 
1505 								// don't delay clients that keep failing to send auth entirely (old Android browsers for example)
1506 
1507 							if ( !pw.equals( "" )){
1508 
1509 								AESemaphore waiter = null;
1510 
1511 								synchronized( fail_map ){
1512 
1513 
1514 									Object[] x = fail_map.get( client_address );
1515 
1516 									if ( x == null ){
1517 
1518 										x = new Object[]{ new AESemaphore( "af:waiter" ), new Long(-1), new Long(-1), now };
1519 
1520 										fail_map.put( client_address, x );
1521 
1522 									}else{
1523 
1524 										x[1] = x[2];
1525 										x[2] = x[3];
1526 										x[3] = now;
1527 
1528 										long t = (Long)x[1];
1529 
1530 										if ( now - t < 10*1000 ){
1531 
1532 											log( "Too many recent authentication failures from '" + client_address + "' - rate limiting" );
1533 
1534 											x[2] = now+DELAY;
1535 											// there's a bug where flipping the password on doesn't reset the pw so we automatically fail without checking
1536 											// this is not the correct fix, but it works
1537 											last_pw = "";
1538 											waiter = (AESemaphore)x[0];
1539 										}
1540 									}
1541 								}
1542 
1543 								if ( waiter != null ){
1544 
1545 									waiter.reserve( DELAY );
1546 								}
1547 							}
1548 						} else {
1549 							// Some clients have no cookie support and will always try with
1550 							// no auth info, then, once getting a failed response, try again
1551 							// with the auth info.
1552 							// This results in a loop of 1 good, 1 bad.
1553 							// Prevent this from causing the "too many recent failures" delay to kick in by removing from map
1554 							// on goodness
1555 
1556 							synchronized( fail_map ){
1557 
1558 								fail_map.remove( client_address );
1559 							}
1560 
1561 							String	cookies = getHeaderField( headers, "Cookie" );
1562 
1563 							if ( pairing_session_code != null ){
1564 
1565 								if ( cookies == null || !cookies.contains( pairing_session_code )){
1566 
1567 									tls.set( pairing_session_code );
1568 								}
1569 							}
1570 						}
1571 
1572 						recordAuthRequest( client_address, result );
1573 
1574 						if ( !result ){
1575 
1576 								// going to be generous here as (old android browsers at least) sometimes fail to provide
1577 								// auth on .png files
1578 
1579 								// no I'm not, too many risks associated with this (e.g. xmwebui has some
1580 								// prefix url logic which may be exploitable)
1581 
1582 							//if ( resource.getPath().endsWith( ".png" )){
1583 							//
1584 							//	result = true;
1585 							//}
1586 						}
1587 
1588 						return( result );
1589 					}
1590 
1591 					private boolean
1592 					authenticateSupport(
1593 						String		headers,
1594 						URL			resource,
1595 						String		user,
1596 						String		pw )
1597 					{
1598 						boolean	result;
1599 
1600 						boolean	auto_auth =  param_auto_auth != null && param_auto_auth.getValue();
1601 
1602 						if ( !pw_enable.getValue()){
1603 
1604 							result = true;
1605 
1606 						}else{
1607 
1608 							if ( auto_auth ){
1609 
1610 								user = user.trim().toLowerCase();
1611 
1612 								pw = pw.toUpperCase();
1613 							}
1614 
1615 							if ( !user.equals( p_user_name.getValue())){
1616 
1617 								result = false;
1618 
1619 							}else{
1620 
1621 								byte[]	hash = last_hash;
1622 
1623 								if (  !last_pw.equals( pw )){
1624 
1625 									hash = plugin_interface.getUtilities().getSecurityManager().calculateSHA1(
1626 											auto_auth?pw.toUpperCase().getBytes():pw.getBytes());
1627 
1628 									last_pw		= pw;
1629 									last_hash	= hash;
1630 								}
1631 
1632 								result = Arrays.equals( hash, p_password.getValue());
1633 							}
1634 						}
1635 
1636 						if ( result ){
1637 
1638 								// user name and password match, see if we've come from the pairing process
1639 
1640 							checkCookieSet( headers, resource );
1641 
1642 						}else if ( auto_auth  ){
1643 
1644 								// either the ac is in the url, referer or we have a cookie set
1645 
1646 							int x = checkCookieSet( headers, resource );
1647 
1648 							if ( x == 1 ){
1649 
1650 								result = true;
1651 
1652 							}else if ( x == 0 ){
1653 
1654 								result = hasOurCookie( getHeaderField( headers, "Cookie" ));
1655 							}
1656 						}else{
1657 
1658 							result = hasOurCookie( getHeaderField( headers, "Cookie" ));
1659 						}
1660 
1661 						return( result );
1662 					}
1663 
1664 						/**
1665 						 *
1666 						 * @param headers
1667 						 * @param resource
1668 						 * @return 0 = unknown, 1 = ok, 2 = bad
1669 						 */
1670 
1671 					private int
1672 					checkCookieSet(
1673 						String		headers,
1674 						URL			resource )
1675 					{
1676 						if ( pairing_access_code == null ){
1677 
1678 							return( 2 );
1679 						}
1680 
1681 						String[]	locations = { resource.getQuery(), getHeaderField( headers, "Referer" )};
1682 
1683 						for ( String location: locations ){
1684 
1685 							if ( location != null ){
1686 
1687 								boolean	skip_fail 	= false;
1688 								int		param_len	= 0;
1689 
1690 								int p1 = location.indexOf( "vuze_pairing_ac=" );
1691 
1692 								if ( p1 == -1 ){
1693 
1694 									p1 = location.indexOf( "ac=" );
1695 
1696 									if ( p1 != -1 ){
1697 
1698 										param_len = 3;
1699 
1700 										skip_fail = true;
1701 									}
1702 								}else{
1703 
1704 									param_len = 16;
1705 								}
1706 
1707 								if ( p1 != -1 ){
1708 
1709 									int p2 = location.indexOf( '&', p1 );
1710 
1711 									String ac = location.substring( p1+param_len, p2==-1?location.length():p2 ).trim();
1712 
1713 									p2 = ac.indexOf( '#' );
1714 
1715 									if ( p2 != -1 ){
1716 
1717 										ac = ac.substring( 0, p2 );
1718 									}
1719 
1720 									if ( ac.equalsIgnoreCase( pairing_access_code )){
1721 
1722 										tls.set( pairing_session_code );
1723 
1724 										return( 1 );
1725 
1726 									}else{
1727 
1728 										if ( !skip_fail ){
1729 
1730 											return( 2 );
1731 										}
1732 									}
1733 								}
1734 							}
1735 						}
1736 
1737 						return( 0 );
1738 					}
1739 
1740 					private String
1741 					getHeaderField(
1742 						String	headers,
1743 						String	field )
1744 					{
1745 						String lc_headers = headers.toLowerCase();
1746 
1747 						int p1 = lc_headers.indexOf( field.toLowerCase() + ":" );
1748 
1749 						if ( p1 != -1 ){
1750 
1751 							int	p2 = lc_headers.indexOf( '\n', p1 );
1752 
1753 							if ( p2 != -1 ){
1754 
1755 								return( headers.substring( p1+field.length()+1, p2 ).trim());
1756 							}
1757 						}
1758 
1759 						return( null );
1760 					}
1761 				});
1762 
1763 		}catch( TrackerException e ){
1764 
1765 			log.log( "Server initialisation failed", e );
1766 		}
1767 	}
1768 
1769 	private boolean
hasOurCookie( String cookies )1770 	hasOurCookie(
1771 		String		cookies )
1772 	{
1773 		if ( cookies == null ){
1774 
1775 			return( false );
1776 		}
1777 
1778 		String[] cookie_list = cookies.split( ";" );
1779 
1780 		for ( String cookie: cookie_list ){
1781 
1782 			String[] bits = cookie.split( "=" );
1783 
1784 			if ( bits.length == 2 ){
1785 
1786 				if ( bits[0].trim().equals( "vuze_pairing_sc" )){
1787 
1788 					if ( bits[1].trim().equals( pairing_session_code )){
1789 
1790 						return( true );
1791 					}
1792 				}
1793 			}
1794 		}
1795 
1796 		return( false );
1797 	}
1798 
1799 	private boolean
sameAddress( InetAddress a1, InetAddress a2 )1800 	sameAddress(
1801 		InetAddress	a1,
1802 		InetAddress a2 )
1803 	{
1804 		if ( a1 == null && a2 == null ){
1805 
1806 			return( true );
1807 
1808 		}else if ( a1 == null || a2 == null ){
1809 
1810 			return( false );
1811 
1812 		}else{
1813 
1814 			return( a1.equals( a2 ));
1815 		}
1816 	}
1817 
1818 	protected void
setupUPnP()1819 	setupUPnP()
1820 	{
1821 		if ( !plugin_enabled  || !p_upnp_enable.getValue()){
1822 
1823 			if ( upnp_mapping != null ){
1824 
1825 				log( "Removing UPnP mapping" );
1826 
1827 				upnp_mapping.destroy();
1828 
1829 				upnp_mapping = null;
1830 			}
1831 
1832 			return;
1833 		}
1834 
1835 		PluginInterface pi_upnp = plugin_interface.getPluginManager().getPluginInterfaceByClass( UPnPPlugin.class );
1836 
1837 		if ( pi_upnp == null ){
1838 
1839 			log.log( "No UPnP plugin available, not attempting port mapping");
1840 
1841 		}else{
1842 
1843 			int port = param_port.getValue();
1844 
1845 			if ( upnp_mapping != null ){
1846 
1847 				if ( upnp_mapping.getPort() == port ){
1848 
1849 					return;
1850 				}
1851 
1852 				log( "Updating UPnP mapping" );
1853 
1854 				upnp_mapping.destroy();
1855 
1856 			}else{
1857 
1858 				log( "Creating UPnP mapping" );
1859 			}
1860 
1861 			upnp_mapping = ((UPnPPlugin)pi_upnp.getPlugin()).addMapping( plugin_interface.getPluginName(), true, port, true );
1862 		}
1863 	}
1864 
1865 	protected void
setupPairing( String sid, boolean pairing_enabled )1866 	setupPairing(
1867 		String		sid,
1868 		boolean		pairing_enabled )
1869 	{
1870 		PairingManager pm = PairingManagerFactory.getSingleton();
1871 
1872 		PairedService service = pm.getService( sid );
1873 
1874 		if ( plugin_enabled && pairing_enabled && pm.isEnabled()){
1875 
1876 			setupAutoAuth();
1877 
1878 			if ( service == null ){
1879 
1880 				log( "Adding pairing service" );
1881 
1882 				service =
1883 					pm.addService(
1884 						sid,
1885 						new PairedServiceRequestHandler()
1886 						{
1887 							public byte[]
1888 							handleRequest(
1889 								InetAddress originator,
1890 								String		endpoint_url,
1891 								byte[] 		request )
1892 
1893 								throws IOException
1894 							{
1895 								return( handleTunnelRequest( originator, endpoint_url, request ));
1896 							}
1897 						});
1898 
1899 				PairingConnectionData cd = service.getConnectionData();
1900 
1901 				try{
1902 					updatePairing( cd );
1903 
1904 				}finally{
1905 
1906 					cd.sync();
1907 				}
1908 			}
1909 		}else{
1910 
1911 			pairing_access_code 	= null;
1912 
1913 			setupSessionCode( null );
1914 
1915 			if ( service != null ){
1916 
1917 				log( "Removing pairing service" );
1918 
1919 				service.remove();
1920 			}
1921 		}
1922 	}
1923 
1924 	private void
setupSessionCode( String key )1925 	setupSessionCode(
1926 		String		key )
1927 	{
1928 		if ( key == null ){
1929 
1930 			key = Base32.encode( p_user_name.getValue().getBytes()) + Base32.encode( p_password.getValue());
1931 		}
1932 
1933 		synchronized( this ){
1934 
1935 			String existing_key = plugin_config.getPluginStringParameter( PAIRING_SESSION_KEY, "" );
1936 
1937 			String[]	bits = existing_key.split( "=" );
1938 
1939 			if ( bits.length == 2 && bits[0].equals( key )){
1940 
1941 				pairing_session_code = bits[1];
1942 
1943 			}else{
1944 
1945 				pairing_session_code = Base32.encode( RandomUtils.nextSecureHash());
1946 
1947 				plugin_config.setPluginParameter( PAIRING_SESSION_KEY, key + "=" + pairing_session_code );
1948 			}
1949 		}
1950 	}
1951 
1952 	protected void
setupAutoAuth()1953 	setupAutoAuth()
1954 	{
1955 		PairingManager pm = PairingManagerFactory.getSingleton();
1956 
1957 		String ac = pm.peekAccessCode();
1958 
1959 		pairing_access_code = ac;
1960 
1961 			// good time to check the default pairing auth settings
1962 
1963 		if ( pairing_access_code != null && param_auto_auth.getValue()){
1964 
1965 			setupSessionCode( ac );
1966 
1967 			try{
1968 				setting_auto_auth = true;
1969 
1970 				if ( !p_user_name.getValue().equals( "vuze" )){
1971 
1972 					p_user_name.setValue( "vuze" );
1973 				}
1974 
1975 		        SHA1Hasher hasher = new SHA1Hasher();
1976 
1977 		        byte[] encoded = hasher.calculateHash( pairing_access_code.getBytes());
1978 
1979 				if ( !Arrays.equals( p_password.getValue(), encoded )){
1980 
1981 					p_password.setValue( pairing_access_code );
1982 				}
1983 
1984 				if ( !pw_enable.getValue()){
1985 
1986 					pw_enable.setValue( true );
1987 				}
1988 			}finally{
1989 
1990 				setting_auto_auth = false;
1991 			}
1992 		}else{
1993 
1994 			setupSessionCode( null );
1995 		}
1996 	}
1997 
1998 	protected void
updatePairing( String sid )1999 	updatePairing(
2000 		String		sid )
2001 	{
2002 		PairingManager pm = PairingManagerFactory.getSingleton();
2003 
2004 		PairedService service = pm.getService( sid );
2005 
2006 		if ( service != null ){
2007 
2008 			PairingConnectionData cd = service.getConnectionData();
2009 
2010 			log( "Updating pairing information" );
2011 
2012 			try{
2013 				updatePairing( cd );
2014 
2015 			}finally{
2016 
2017 				cd.sync();
2018 			}
2019 		}
2020 	}
2021 
2022 	protected void
updatePairing( PairingConnectionData cd )2023 	updatePairing(
2024 		PairingConnectionData		cd )
2025 	{
2026 		cd.setAttribute( PairingConnectionData.ATTR_PORT, 		String.valueOf( param_port.getValue()));
2027 
2028 		int	override = param_port_or==null?0:param_port_or.getValue();
2029 
2030 		if ( override > 0 ){
2031 
2032 			cd.setAttribute( PairingConnectionData.ATTR_PORT_OVERRIDE, 	String.valueOf( override ));
2033 
2034 		}else{
2035 
2036 			cd.setAttribute( PairingConnectionData.ATTR_PORT_OVERRIDE, null );
2037 		}
2038 
2039 		cd.setAttribute( PairingConnectionData.ATTR_PROTOCOL, 	param_protocol.getValue());
2040 
2041 		if ( param_i2p_dest.isVisible()){
2042 
2043 			String host = param_i2p_dest.getValue();
2044 
2045 			if ( host.length() > 0 ){
2046 
2047 				cd.setAttribute( PairingConnectionData.ATTR_I2P, host );
2048 			}
2049 		}
2050 
2051 		if ( param_tor_dest.isVisible()){
2052 
2053 			String host = param_tor_dest.getValue();
2054 
2055 			if ( host.length() > 0 ){
2056 
2057 				cd.setAttribute( PairingConnectionData.ATTR_TOR, host );
2058 			}
2059 		}
2060 	}
2061 
2062 	public InetAddress
getServerBindIP()2063 	getServerBindIP()
2064 	{
2065 		if ( tracker_context == null ){
2066 
2067 			return( new InetSocketAddress(0).getAddress());
2068 		}
2069 
2070 		InetAddress address = tracker_context.getBindIP();
2071 
2072 		if ( address == null ){
2073 
2074 			return( new InetSocketAddress(0).getAddress());
2075 		}
2076 
2077 		return( address );
2078 	}
2079 
2080 	public int
getServerPort()2081 	getServerPort()
2082 	{
2083 		if ( tracker_context == null ){
2084 
2085 			return( 0 );
2086 		}
2087 
2088 		URL	url = tracker_context.getURLs()[0];
2089 
2090 		return( url.getPort()==-1?url.getDefaultPort():url.getPort());
2091 	}
2092 
2093 	public int
getPort()2094 	getPort()
2095 	{
2096 		return( param_port.getValue());
2097 	}
2098 
2099 	public String
getProtocol()2100 	getProtocol()
2101 	{
2102 		return( param_protocol.getValue());
2103 	}
2104 
2105 	public void
setUserAndPassword( String user, String password )2106 	setUserAndPassword(
2107 		String		user,
2108 		String		password )
2109 	{
2110 		p_user_name.setValue( user );
2111 		p_password.setValue( password );
2112 		pw_enable.setValue( true );
2113 	}
2114 
2115 	public void
unsetUserAndPassword()2116 	unsetUserAndPassword()
2117 	{
2118 		pw_enable.setValue( false );
2119 	}
2120 
2121 	private void
recordAuthRequest( String client_ip, boolean good )2122 	recordAuthRequest(
2123 		String						client_ip,
2124 		boolean						good )
2125 	{
2126 		PairingManager pm = PairingManagerFactory.getSingleton();
2127 
2128 		pm.recordRequest( plugin_interface.getPluginName(), client_ip, good );
2129 	}
2130 
2131 	private void
recordRequest( TrackerWebPageRequest request, boolean good, boolean is_tunnel )2132 	recordRequest(
2133 		TrackerWebPageRequest		request,
2134 		boolean						good,
2135 		boolean						is_tunnel )
2136 	{
2137 		PairingManager pm = PairingManagerFactory.getSingleton();
2138 
2139 		String	str = request.getClientAddress();
2140 
2141 		if ( is_tunnel ){
2142 
2143 			str = "Tunnel (" + str + ")";
2144 		}
2145 
2146 		pm.recordRequest( plugin_interface.getPluginName(), str, good );
2147 	}
2148 
2149 	public boolean
generateSupport( TrackerWebPageRequest request, TrackerWebPageResponse response )2150 	generateSupport(
2151 		TrackerWebPageRequest		request,
2152 		TrackerWebPageResponse		response )
2153 
2154 		throws IOException
2155 	{
2156 		return( false );
2157 	}
2158 
2159 	private byte[]
handleTunnelRequest( final InetAddress originator, String endpoint_url, final byte[] request_bytes )2160 	handleTunnelRequest(
2161 		final InetAddress		originator,
2162 		String					endpoint_url,
2163 		final byte[]			request_bytes )
2164 
2165 		throws IOException
2166 	{
2167 		int	q_pos = endpoint_url.indexOf( '?' );
2168 
2169 		boolean	raw = true;
2170 
2171 		if ( q_pos != -1 ){
2172 
2173 			String params = endpoint_url.substring( q_pos+1 );
2174 
2175 			String[] args = params.split( "&" );
2176 
2177 			String new_endpoint = endpoint_url.substring( 0, q_pos );
2178 
2179 			String	sep = "?";
2180 
2181 			for ( String arg: args ){
2182 
2183 				if ( arg.startsWith( "tunnel_format=" )){
2184 
2185 					String temp = arg.substring( 14 );
2186 
2187 					if ( temp.startsWith( "h" )){
2188 
2189 						raw = false;
2190 					}
2191 				}else{
2192 
2193 					new_endpoint += sep + arg;
2194 
2195 					sep = "&";
2196 				}
2197 			}
2198 
2199 			endpoint_url = new_endpoint;
2200 		}
2201 
2202 		final String		f_endpoint_url	= endpoint_url;
2203 		final JSONObject	request_headers = new JSONObject();
2204 
2205 		final int			data_start;
2206 
2207 		if ( raw ){
2208 
2209 			data_start = 0;
2210 
2211 		}else{
2212 			int	request_header_len = ((request_bytes[0]<<8)&0x0000ff00) | (request_bytes[1]&0x000000ff);
2213 
2214 			String	reply_json_str = new String( request_bytes, 2, request_header_len, "UTF-8" );
2215 
2216 			request_headers.putAll( JSONUtils.decodeJSON( reply_json_str ));
2217 
2218 			data_start = request_header_len + 2;
2219 		}
2220 
2221 		TrackerWebPageRequest request =
2222 			new TrackerWebPageRequest()
2223 			{
2224 				public Tracker
2225 				getTracker()
2226 				{
2227 					return( null );
2228 				}
2229 
2230 				public String
2231 				getClientAddress()
2232 				{
2233 					return( originator.getHostAddress());
2234 				}
2235 
2236 				public InetSocketAddress
2237 				getClientAddress2()
2238 				{
2239 					return( new InetSocketAddress( originator, 0 ));
2240 				}
2241 
2242 				public InetSocketAddress
2243 				getLocalAddress()
2244 				{
2245 					return( new InetSocketAddress( "127.0.0.1", 0 ));
2246 				}
2247 
2248 				public String
2249 				getUser()
2250 				{
2251 					return( null );
2252 				}
2253 
2254 				public String
2255 				getURL()
2256 				{
2257 					String url = (String)request_headers.get( "HTTP-URL" );
2258 
2259 					if ( url != null ){
2260 
2261 						return( url );
2262 					}
2263 
2264 					return( f_endpoint_url );
2265 				}
2266 
2267 				public String
2268 				getHeader()
2269 				{
2270 					return( "" );
2271 				}
2272 
2273 				public Map
2274 				getHeaders()
2275 				{
2276 					return( request_headers );
2277 				}
2278 
2279 				public InputStream
2280 				getInputStream()
2281 				{
2282 					return( new ByteArrayInputStream( request_bytes, data_start, request_bytes.length - data_start ));
2283 				}
2284 
2285 				public URL
2286 				getAbsoluteURL()
2287 				{
2288 					try{
2289 						return( new URL( "http://127.0.0.1" + getURL()));
2290 
2291 					}catch( Throwable e ){
2292 
2293 						return( null );
2294 					}
2295 				}
2296 
2297 				public TrackerWebContext
2298 				getContext()
2299 				{
2300 					return( null );
2301 				}
2302 			};
2303 		final ByteArrayOutputStream	baos = new ByteArrayOutputStream();
2304 
2305 		final Map	reply_headers	= new HashMap();
2306 
2307 		TrackerWebPageResponse	response =
2308 			new TrackerWebPageResponse()
2309 			{
2310 				public OutputStream
2311 				getOutputStream()
2312 				{
2313 					return( baos );
2314 				}
2315 
2316 				public void
2317 				setReplyStatus(
2318 					int		status )
2319 				{
2320 					reply_headers.put( "HTTP-Status", String.valueOf( status ));
2321 				}
2322 
2323 				public void
2324 				setContentType(
2325 					String		type )
2326 				{
2327 					reply_headers.put( "Content-Type", type );
2328 				}
2329 
2330 				public void
2331 				setLastModified(
2332 					long		time )
2333 				{
2334 				}
2335 
2336 				public void
2337 				setExpires(
2338 					long		time )
2339 				{
2340 				}
2341 
2342 				public void
2343 				setHeader(
2344 					String		name,
2345 					String		value )
2346 				{
2347 					reply_headers.put( name, value );
2348 				}
2349 
2350 				public void
2351 				setGZIP(
2352 					boolean		gzip )
2353 				{
2354 				}
2355 
2356 				public boolean
2357 				useFile(
2358 					String		root_dir,
2359 					String		relative_url )
2360 
2361 					throws IOException
2362 				{
2363 					Debug.out( "Not supported" );
2364 
2365 					return( false );
2366 				}
2367 
2368 				public void
2369 				useStream(
2370 					String		file_type,
2371 					InputStream	stream )
2372 
2373 					throws IOException
2374 				{
2375 					Debug.out( "Not supported" );
2376 				}
2377 
2378 				public void
2379 				writeTorrent(
2380 					TrackerTorrent	torrent )
2381 
2382 					throws IOException
2383 				{
2384 					Debug.out( "Not supported" );
2385 				}
2386 
2387 				public void
2388 				setAsynchronous(
2389 					boolean		async )
2390 
2391 					throws IOException
2392 				{
2393 					Debug.out( "Not supported" );
2394 				}
2395 
2396 				public boolean
2397 				getAsynchronous()
2398 				{
2399 					return( false );
2400 				}
2401 
2402 				public OutputStream
2403 				getRawOutputStream()
2404 
2405 					throws IOException
2406 				{
2407 					Debug.out( "Not supported" );
2408 
2409 					throw( new IOException( "Not supported" ));
2410 				}
2411 
2412 				public boolean
2413 				isActive()
2414 				{
2415 					return( true );
2416 				}
2417 			};
2418 
2419 		try{
2420 			byte[]		bytes;
2421 
2422 			if ( generate2( request, response, true )){
2423 
2424 				bytes = baos.toByteArray();
2425 
2426 			}else{
2427 
2428 				Debug.out( "Tunnelled request not handled: " + request.getURL());
2429 
2430 				response.setReplyStatus( 404 );
2431 
2432 				bytes = new byte[0];
2433 			}
2434 
2435 			if ( raw ){
2436 
2437 				return( bytes );
2438 
2439 			}else{
2440 
2441 				String accept_encoding = (String)request_headers.get( "Accept-Encoding" );
2442 
2443 				if ( accept_encoding != null && accept_encoding.contains( "gzip" )){
2444 
2445 					reply_headers.put( "Content-Encoding", "gzip" );
2446 
2447 					ByteArrayOutputStream	temp = new ByteArrayOutputStream( bytes.length + 512 );
2448 
2449 					GZIPOutputStream gos = new GZIPOutputStream( temp );
2450 
2451 					gos.write( bytes );
2452 
2453 					gos.close();
2454 
2455 					bytes = temp.toByteArray();
2456 				}
2457 
2458 				ByteArrayOutputStream baos2 = new ByteArrayOutputStream( bytes.length + 512 );
2459 
2460 				String header_json = JSONUtils.encodeToJSON( reply_headers );
2461 
2462 				byte[] header_bytes = header_json.getBytes( "UTF-8" );
2463 
2464 				int	header_len = header_bytes.length;
2465 
2466 				byte[] header_len_bytes = new byte[]{ (byte)(header_len>>8), (byte)header_len };
2467 
2468 				baos2.write( header_len_bytes );
2469 				baos2.write( header_bytes );
2470 				baos2.write( bytes );
2471 
2472 				return( baos2.toByteArray());
2473 			}
2474 		}catch( Throwable e ){
2475 
2476 			Debug.out( e );
2477 
2478 			return( new byte[0] );
2479 		}
2480 	}
2481 
2482 	public boolean
generate( TrackerWebPageRequest request, TrackerWebPageResponse response )2483 	generate(
2484 		TrackerWebPageRequest		request,
2485 		TrackerWebPageResponse		response )
2486 
2487 		throws IOException
2488 	{
2489 		String url = request.getURL();
2490 
2491 		if ( url.startsWith( "/pairing/tunnel/" )){
2492 
2493 			long	error_code = 1;
2494 
2495 			try{
2496 				final PairingManager pm = PairingManagerFactory.getSingleton();
2497 
2498 				if ( pm.isEnabled()){
2499 
2500 					if ( pm.isSRPEnabled()){
2501 
2502 						return( pm.handleLocalTunnel( request, response ));
2503 
2504 					}else{
2505 
2506 						error_code = 5;
2507 
2508 						throw( new IOException( "Secure pairing is not enabled" ));
2509 					}
2510 				}else{
2511 
2512 					error_code = 5;
2513 
2514 					throw( new IOException( "Pairing is not enabled" ));
2515 				}
2516 			}catch( Throwable e ){
2517 
2518 				JSONObject json = new JSONObject();
2519 
2520 				JSONObject error = new JSONObject();
2521 
2522 				json.put( "error", error );
2523 
2524 				error.put( "msg", Debug.getNestedExceptionMessage(e));
2525 				error.put( "code", error_code );
2526 
2527 				return( returnJSON( response, JSONUtils.encodeToJSON( json )));
2528 			}
2529 		}
2530 
2531 		return( generate2( request, response, false ));
2532 	}
2533 
2534 	private boolean
generate2( TrackerWebPageRequest request, TrackerWebPageResponse response, boolean is_tunnel )2535 	generate2(
2536 		TrackerWebPageRequest		request,
2537 		TrackerWebPageResponse		response,
2538 		boolean						is_tunnel )
2539 
2540 		throws IOException
2541 	{
2542 		// System.out.println( request.getURL());
2543 
2544 		String	client = request.getClientAddress();
2545 
2546 		if ( !ip_range_all ){
2547 
2548 			// System.out.println( "client = " + client );
2549 
2550 			try{
2551 				boolean valid_ip = true;
2552 
2553 				InetAddress client_ia = InetAddress.getByName( client );
2554 
2555 				if ( ip_ranges == null ){
2556 
2557 					if ( !client_ia.isLoopbackAddress()){
2558 
2559 						InetAddress bind_ia = getServerBindIP();
2560 
2561 						if ( bind_ia.isAnyLocalAddress() || !bind_ia.equals( client_ia )){
2562 
2563 							log.log( LoggerChannel.LT_ERROR, "Client '" + client + "' is not local, rejecting" );
2564 
2565 							valid_ip = false;
2566 						}
2567 					}
2568 				}else{
2569 
2570 					boolean ok = false;
2571 
2572 					for ( IPRange range: ip_ranges ){
2573 
2574 						if ( range.isInRange( client_ia.getHostAddress())){
2575 
2576 							ok = true;
2577 						}
2578 					}
2579 
2580 					if ( !ok ){
2581 
2582 						log.log( LoggerChannel.LT_ERROR, "Client '" + client + "' (" + client_ia.getHostAddress() + ") is not in range, rejecting" );
2583 
2584 						valid_ip = false;
2585 					}
2586 				}
2587 
2588 				if ( !valid_ip ){
2589 
2590 					response.setReplyStatus( 403 );
2591 
2592 					recordRequest( request, false, is_tunnel );
2593 
2594 					return( returnTextPlain( response, "Cannot access resource from this IP address." ));
2595 				}
2596 
2597 			}catch( Throwable e ){
2598 
2599 				Debug.printStackTrace( e );
2600 
2601 				recordRequest( request, false, is_tunnel );
2602 
2603 				return( false );
2604 			}
2605 		}
2606 
2607 		recordRequest( request, true, is_tunnel );
2608 
2609 		String url = request.getURL();
2610 
2611 		if ( url.toString().endsWith(".class")){
2612 
2613 			System.out.println( "WebPlugin::generate:" + url );
2614 		}
2615 
2616 		String	cookie_to_set = tls.get();
2617 
2618 		if ( cookie_to_set == GRACE_PERIOD_MARKER ){
2619 
2620 			return( returnTextPlain( response, "Logout in progress, please try again later." ));
2621 		}
2622 
2623 		if ( cookie_to_set != null ){
2624 
2625 				// set session cookie
2626 
2627 			response.setHeader( "Set-Cookie", "vuze_pairing_sc=" + cookie_to_set + "; path=/; HttpOnly" );
2628 
2629 			tls.set( null );
2630 		}
2631 
2632 		URL full_url = request.getAbsoluteURL();
2633 
2634 		String	full_url_path = full_url.getPath();
2635 
2636 		if ( full_url_path.equals( "/isPairedServiceAvailable" )){
2637 
2638 			String redirect = getArgumentFromURL( full_url, "redirect_to" );
2639 
2640 			if ( redirect != null ){
2641 
2642 				try{
2643 					URL target = new URL( redirect );
2644 
2645 					String	host = target.getHost();
2646 
2647 					if ( !Constants.isAzureusDomain( host )){
2648 
2649 						if ( !InetAddress.getByName(host).isLoopbackAddress()){
2650 
2651 							log( "Invalid redirect host: " + host );
2652 
2653 							redirect = null;
2654 						}
2655 					}
2656 				}catch( Throwable e ){
2657 
2658 					Debug.out( e );
2659 
2660 					redirect = null;
2661 				}
2662 			}
2663 
2664 			if ( redirect != null ){
2665 
2666 				response.setReplyStatus( 302 );
2667 
2668 				response.setHeader( "Location", redirect );
2669 
2670 				return( true );
2671 			}
2672 
2673 			String callback = getArgumentFromURL( full_url, "jsoncallback" );
2674 
2675 			if ( callback != null ){
2676 
2677 				return( returnTextPlain( response,  callback + "( {'pairedserviceavailable':true} )"));
2678 			}
2679 		}else if ( full_url_path.equals( "/isServicePaired" )){
2680 
2681 			boolean paired = cookie_to_set != null || hasOurCookie((String)request.getHeaders().get( "cookie" ));
2682 
2683 				// DON'T use returnJSON here as it DOESN'T work in the web ui for some reason!
2684 
2685 			return( returnTextPlain( response, "{ 'servicepaired': " + ( paired?"true":"false" ) + " }" ));
2686 
2687 		}else if ( full_url_path.equals( "/pairedServiceLogout")){
2688 
2689 			synchronized( logout_timer ){
2690 
2691 				logout_timer.put( client, SystemTime.getMonotonousTime());
2692 			}
2693 
2694 			response.setHeader( "Set-Cookie", "vuze_pairing_sc=<deleted>, expires=" + TimeFormatter.getCookieDate(0));
2695 
2696 			String redirect = getArgumentFromURL( full_url, "redirect_to" );
2697 
2698 			if ( redirect != null ){
2699 
2700 				try{
2701 					URL target = new URL( redirect );
2702 
2703 					String	host = target.getHost();
2704 
2705 					if ( !Constants.isAzureusDomain( host )){
2706 
2707 						if ( !InetAddress.getByName(host).isLoopbackAddress()){
2708 
2709 							log( "Invalid redirect host: " + host );
2710 
2711 							redirect = null;
2712 						}
2713 					}
2714 				}catch( Throwable e ){
2715 
2716 					Debug.out( e );
2717 
2718 					redirect = null;
2719 				}
2720 			}
2721 			if ( redirect == null ){
2722 
2723 				return( returnTextPlain( response, "" ));
2724 
2725 			}else{
2726 
2727 				response.setReplyStatus( 302 );
2728 
2729 				response.setHeader( "Location", redirect );
2730 
2731 				return( true );
2732 			}
2733 		}
2734 
2735 		request.getHeaders().put( "x-vuze-is-tunnel", is_tunnel?"true":"false" );
2736 
2737 		if ( generateSupport( request, response )){
2738 
2739 			return(true);
2740 		}
2741 
2742 		if ( is_tunnel ){
2743 
2744 			return( false );
2745 		}
2746 
2747 		if ( url.equals("/") || url.startsWith( "/?" )){
2748 
2749 			url = "/";
2750 
2751 			if ( home_page != null ){
2752 
2753 				url = home_page;
2754 
2755 			}else{
2756 
2757 				for (int i=0;i<welcome_files.length;i++){
2758 
2759 					if ( welcome_files[i].exists()){
2760 
2761 						url = "/" + welcome_pages[i];
2762 
2763 						break;
2764 					}
2765 				}
2766 			}
2767 		}
2768 
2769 			// first try file system for data
2770 
2771 		if ( useFile( request, response, file_root, UrlUtils.decode( url ))){
2772 
2773 			return( true );
2774 		}
2775 
2776 				// now try jars
2777 
2778 		String	resource_name = url;
2779 
2780 		if (resource_name.startsWith("/")){
2781 
2782 			resource_name = resource_name.substring(1);
2783 		}
2784 
2785 		int	pos = resource_name.lastIndexOf(".");
2786 
2787 		if ( pos != -1 ){
2788 
2789 			String	type = resource_name.substring( pos+1 );
2790 
2791 			ClassLoader	cl = plugin_interface.getPluginClassLoader();
2792 
2793 			InputStream is = cl.getResourceAsStream( resource_name );
2794 
2795 			if ( is == null ){
2796 
2797 				// failed absolute load, try relative
2798 
2799 				if ( resource_root != null ){
2800 
2801 					resource_name = resource_root + "/" + resource_name;
2802 
2803 					is = cl.getResourceAsStream( resource_name );
2804 				}
2805 			}
2806 
2807 			// System.out.println( resource_name + "->" + is + ", url = " + url );
2808 
2809 			if (is != null ){
2810 
2811 				try{
2812 					response.useStream( type, is );
2813 
2814 				}finally{
2815 
2816 					is.close();
2817 				}
2818 
2819 				return( true );
2820 			}
2821 		}
2822 
2823 		return( false );
2824 	}
2825 
2826 		/**
2827 		 * this method can be over-ridden to handle custom file delivery
2828 		 * @param request
2829 		 * @param response
2830 		 * @param root
2831 		 * @param relative_url
2832 		 * @return
2833 		 * @throws IOException
2834 		 */
2835 
2836 	protected boolean
useFile( TrackerWebPageRequest request, TrackerWebPageResponse response, String root, String relative_url )2837 	useFile(
2838 		TrackerWebPageRequest		request,
2839 		TrackerWebPageResponse		response,
2840 		String						root,
2841 		String						relative_url )
2842 
2843 		throws IOException
2844 	{
2845 		return( response.useFile( file_root, relative_url ));
2846 	}
2847 
2848 	private String
getArgumentFromURL( URL url, String argument )2849 	getArgumentFromURL(
2850 		URL			url,
2851 		String		argument )
2852 	{
2853 		String query = url.getQuery();
2854 
2855 		if ( query != null ){
2856 
2857 			String[] args = query.split( "&" );
2858 
2859 			for ( String arg: args ){
2860 
2861 				String [] x = arg.split( "=" );
2862 
2863 				if ( x.length == 2 ){
2864 
2865 					if ( x[0].equals( argument )){
2866 
2867 						return( UrlUtils.decode( x[1] ));
2868 					}
2869 				}
2870 			}
2871 		}
2872 
2873 		return( null );
2874 	}
2875 
2876 	private boolean
returnTextPlain( TrackerWebPageResponse response, String str )2877 	returnTextPlain(
2878 		TrackerWebPageResponse		response,
2879 		String						str )
2880 	{
2881 		return( returnStuff( response, "text/plain", str ));
2882 	}
2883 
2884 	private boolean
returnJSON( TrackerWebPageResponse response, String str )2885 	returnJSON(
2886 		TrackerWebPageResponse		response,
2887 		String						str )
2888 
2889 		throws IOException
2890 	{
2891 		response.setContentType( "application/json; charset=UTF-8" );
2892 
2893 		OutputStream os = response.getOutputStream();
2894 
2895 		os.write( str.getBytes( "UTF-8" ));
2896 
2897 		return( true );
2898 	}
2899 
2900 	private boolean
returnStuff( TrackerWebPageResponse response, String content_type, String str )2901 	returnStuff(
2902 		TrackerWebPageResponse		response,
2903 		String						content_type,
2904 		String						str )
2905 	{
2906 		response.setContentType( content_type );
2907 
2908 		PrintWriter pw = new PrintWriter( response.getOutputStream());
2909 
2910 		pw.println( str );
2911 
2912 		pw.flush();
2913 
2914 		pw.close();
2915 
2916 		return( true );
2917 	}
2918 
2919 	protected BasicPluginConfigModel
getConfigModel()2920 	getConfigModel()
2921 	{
2922 		return( config_model );
2923 	}
2924 
getViewModel()2925 	protected BasicPluginViewModel getViewModel() {
2926 		return this.view_model;
2927 	}
2928 
2929 	protected void
log( String str )2930 	log(
2931 		String	str )
2932 	{
2933 		log.log( str );
2934 	}
2935 
2936 	protected void
log( String str, Throwable e )2937 	log(
2938 		String		str,
2939 		Throwable 	e )
2940 	{
2941 		log.log( str, e );
2942 	}
2943 }
2944