Commit 3f76e421 authored by Guus der Kinderen's avatar Guus der Kinderen

OF-512: Allow 'reported' streamhost to be modified.

The file transfer proxy will listen on, and report, a set of network interfaces. Sometimes, the proxy
is not reachable on these addresses directly (for instance in a network environment where NAT comes
into play).

This commit re-purposes the 'xmpp.proxy.externalip' property. Earlier, this was used to make the proxy
bind to a specific interface. As this is quite similar to the functionality provided by the
'network.interface' property, 'xmpp.proxy.externalip' now controls only what interface is reported. It
no longer affect the interface binding process directly. This functionality has also been added to the
admin console.
parent 623e6c85
...@@ -2705,9 +2705,12 @@ filetransferproxy.settings.label_disable=Disabled ...@@ -2705,9 +2705,12 @@ filetransferproxy.settings.label_disable=Disabled
filetransferproxy.settings.label_disable_info=This server will not act as a file transfer proxy. filetransferproxy.settings.label_disable_info=This server will not act as a file transfer proxy.
filetransferproxy.settings.label_enable=Enabled filetransferproxy.settings.label_enable=Enabled
filetransferproxy.settings.label_enable_info=This server will act as a file transfer proxy on port: filetransferproxy.settings.label_enable_info=This server will act as a file transfer proxy on port:
filetransferproxy.settings.label_port=Port:
filetransferproxy.settings.label_hardcoded_address=Override reported address
filetransferproxy.settings.label_hardcoded_optionality=(leave empty if an override is not needed)
filetransferproxy.settings.valid.port=Please enter a valid port. filetransferproxy.settings.valid.port=Please enter a valid port.
filetransferproxy.settings.confirm.updated=File transfer proxy settings updated succesfully. filetransferproxy.settings.confirm.updated=File transfer proxy settings updated succesfully.
filetransfer.error.disabled=File Transfer Has Been Disabled, Proxy Won't Function filetransfer.error.disabled=File Transfer Has Been Disabled, Proxy Won't Function.
# File Transfer Proxy Stats # File Transfer Proxy Stats
......
...@@ -63,8 +63,8 @@ import org.xmpp.packet.PacketError; ...@@ -63,8 +63,8 @@ import org.xmpp.packet.PacketError;
public class FileTransferProxy extends BasicModule public class FileTransferProxy extends BasicModule
implements ServerItemsProvider, DiscoInfoProvider, DiscoItemsProvider, implements ServerItemsProvider, DiscoInfoProvider, DiscoItemsProvider,
RoutableChannelHandler { RoutableChannelHandler {
private static final Logger Log = LoggerFactory.getLogger(FileTransferProxy.class); private static final Logger Log = LoggerFactory.getLogger( FileTransferProxy.class);
/** /**
* The JiveProperty relating to whether or not the file treansfer proxy is enabled. * The JiveProperty relating to whether or not the file treansfer proxy is enabled.
...@@ -77,6 +77,11 @@ public class FileTransferProxy extends BasicModule ...@@ -77,6 +77,11 @@ public class FileTransferProxy extends BasicModule
*/ */
public static final String JIVEPROPERTY_PORT = "xmpp.proxy.port"; public static final String JIVEPROPERTY_PORT = "xmpp.proxy.port";
/**
* Name of the property that hardcodes the external IP that is being listened on.
*/
public static final String PROPERTY_EXTERNALIP = "xmpp.proxy.externalip";
/** /**
* Whether or not the file transfer proxy is enabled by default. * Whether or not the file transfer proxy is enabled by default.
*/ */
...@@ -132,15 +137,28 @@ public class FileTransferProxy extends BasicModule ...@@ -132,15 +137,28 @@ public class FileTransferProxy extends BasicModule
else if (FileTransferManager.NAMESPACE_BYTESTREAMS.equals(namespace)) { else if (FileTransferManager.NAMESPACE_BYTESTREAMS.equals(namespace)) {
if (packet.getType() == IQ.Type.get) { if (packet.getType() == IQ.Type.get) {
IQ reply = IQ.createResultIQ(packet); IQ reply = IQ.createResultIQ(packet);
Element newChild = reply.setChildElement("query", Element newChild = reply.setChildElement("query", FileTransferManager.NAMESPACE_BYTESTREAMS);
FileTransferManager.NAMESPACE_BYTESTREAMS);
for ( InetAddress address : getAddresses() ) final String externalIP = JiveGlobals.getProperty( PROPERTY_EXTERNALIP );
if ( externalIP != null && !externalIP.isEmpty() )
{ {
Element response = newChild.addElement( "streamhost" ); // OF-512: Override the automatic detection with a specific address (useful for NATs, proxies, etc)
final Element response = newChild.addElement( "streamhost" );
response.addAttribute( "jid", getServiceDomain() ); response.addAttribute( "jid", getServiceDomain() );
response.addAttribute( "host", address.getHostAddress() ); response.addAttribute( "host", externalIP );
response.addAttribute( "port", String.valueOf( connectionManager.getProxyPort() ) ); response.addAttribute( "port", String.valueOf( connectionManager.getProxyPort() ) );
} }
else
{
// Report all network addresses that we know that we're servicing.
for ( final InetAddress address : getAddresses() )
{
final Element response = newChild.addElement( "streamhost" );
response.addAttribute( "jid", getServiceDomain() );
response.addAttribute( "host", address.getHostAddress() );
response.addAttribute( "port", String.valueOf( connectionManager.getProxyPort() ) );
}
}
router.route(reply); router.route(reply);
return true; return true;
} }
...@@ -179,71 +197,54 @@ public class FileTransferProxy extends BasicModule ...@@ -179,71 +197,54 @@ public class FileTransferProxy extends BasicModule
routingTable = server.getRoutingTable(); routingTable = server.getRoutingTable();
router = server.getPacketRouter(); router = server.getPacketRouter();
final String hardCodedProxyIP = JiveGlobals.getProperty( "xmpp.proxy.externalip" ); connectionManager = new ProxyConnectionManager(getFileTransferManager(server));
}
/**
* Returns the IP address(es) that the proxy connection manager is servicing.
*/
private Set<InetAddress> getAddresses()
{
final String interfaceName = JiveGlobals.getXMLProperty( "network.interface" ); final String interfaceName = JiveGlobals.getXMLProperty( "network.interface" );
if ( hardCodedProxyIP != null && !hardCodedProxyIP.trim().isEmpty() )
{ final Set<InetAddress> result = new HashSet<>();
// First choice: a hardcoded IP address, if one exists.
try // Let's see if we hardcoded a specific interface, then use its address.
{ if ( interfaceName != null && !interfaceName.trim().isEmpty() )
bindInterface = InetAddress.getByName( hardCodedProxyIP.trim() );
}
catch ( UnknownHostException e )
{
Log.error( "Error binding to xmpp.proxy.externalip '{}'", interfaceName, e );
}
}
else if ( interfaceName != null && !interfaceName.trim().isEmpty() )
{ {
// No hardcoded IP? Let's see if we hardcoded a specific interface, then use its address.
try try
{ {
bindInterface = InetAddress.getByName( interfaceName.trim() ); bindInterface = InetAddress.getByName( interfaceName.trim() );
result.add( bindInterface );
return result;
} }
catch ( UnknownHostException e ) catch ( UnknownHostException e )
{ {
Log.error( "Error binding to network.interface '{}'", interfaceName, e ); Log.error( "Error binding to network.interface '{}'", interfaceName, e );
} }
} }
else
{
// If no configuration is available, use all available addresses.
bindInterface = null;
}
connectionManager = new ProxyConnectionManager(getFileTransferManager(server));
}
/** // When there's no specific address configured, return all available (non-loopback) addresses.
* Returns the IP address(es) that are used. try
*/
private Set<InetAddress> getAddresses()
{
final Set<InetAddress> result = new HashSet<>();
if ( bindInterface != null )
{ {
result.add( bindInterface ); final Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
} while ( networkInterfaces.hasMoreElements() )
else
{
// When there's no specific address configured, return all available addresses.
try
{ {
final Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); final NetworkInterface networkInterface = networkInterfaces.nextElement();
while ( networkInterfaces.hasMoreElements() ) if ( networkInterface.isLoopback() )
{ {
final NetworkInterface networkInterface = networkInterfaces.nextElement(); continue;
final Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses(); }
while ( inetAddresses.hasMoreElements() ) final Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
{ while ( inetAddresses.hasMoreElements() )
result.add( inetAddresses.nextElement() ); {
} result.add( inetAddresses.nextElement() );
} }
} }
catch ( SocketException e ) }
{ catch ( SocketException e )
Log.error( "Error determining all addresses for this server", e ); {
} Log.error( "Error determining all addresses for this server", e );
} }
return result; return result;
} }
...@@ -253,7 +254,7 @@ public class FileTransferProxy extends BasicModule ...@@ -253,7 +254,7 @@ public class FileTransferProxy extends BasicModule
} }
@Override @Override
public void start() { public void start() {
super.start(); super.start();
if (isEnabled()) { if (isEnabled()) {
...@@ -273,7 +274,7 @@ public class FileTransferProxy extends BasicModule ...@@ -273,7 +274,7 @@ public class FileTransferProxy extends BasicModule
} }
@Override @Override
public void stop() { public void stop() {
super.stop(); super.stop();
XMPPServer.getInstance().getIQDiscoItemsHandler() XMPPServer.getInstance().getIQDiscoItemsHandler()
...@@ -283,7 +284,7 @@ public class FileTransferProxy extends BasicModule ...@@ -283,7 +284,7 @@ public class FileTransferProxy extends BasicModule
} }
@Override @Override
public void destroy() { public void destroy() {
super.destroy(); super.destroy();
connectionManager.shutdown(); connectionManager.shutdown();
...@@ -291,7 +292,8 @@ public class FileTransferProxy extends BasicModule ...@@ -291,7 +292,8 @@ public class FileTransferProxy extends BasicModule
public void enableFileTransferProxy(boolean isEnabled) { public void enableFileTransferProxy(boolean isEnabled) {
JiveGlobals.setProperty(FileTransferProxy.JIVEPROPERTY_PROXY_ENABLED, JiveGlobals.setProperty(FileTransferProxy.JIVEPROPERTY_PROXY_ENABLED,
Boolean.toString(isEnabled)); Boolean.toString(isEnabled));
setEnabled( isEnabled );
} }
private void setEnabled(boolean isEnabled) { private void setEnabled(boolean isEnabled) {
...@@ -358,9 +360,9 @@ public class FileTransferProxy extends BasicModule ...@@ -358,9 +360,9 @@ public class FileTransferProxy extends BasicModule
} }
final DiscoServerItem item = new DiscoServerItem(new JID( final DiscoServerItem item = new DiscoServerItem(new JID(
getServiceDomain()), "Socks 5 Bytestreams Proxy", null, null, this, getServiceDomain()), "Socks 5 Bytestreams Proxy", null, null, this,
this); this);
return Collections.singleton(item).iterator(); return Collections.singleton(item).iterator();
} }
...@@ -378,7 +380,7 @@ public class FileTransferProxy extends BasicModule ...@@ -378,7 +380,7 @@ public class FileTransferProxy extends BasicModule
@Override @Override
public Iterator<String> getFeatures(String name, String node, JID senderJID) { public Iterator<String> getFeatures(String name, String node, JID senderJID) {
return Arrays.asList(FileTransferManager.NAMESPACE_BYTESTREAMS, return Arrays.asList(FileTransferManager.NAMESPACE_BYTESTREAMS,
"http://jabber.org/protocol/disco#info").iterator(); "http://jabber.org/protocol/disco#info").iterator();
} }
@Override @Override
...@@ -415,11 +417,21 @@ public class FileTransferProxy extends BasicModule ...@@ -415,11 +417,21 @@ public class FileTransferProxy extends BasicModule
private class FileTransferPropertyListener implements PropertyEventListener { private class FileTransferPropertyListener implements PropertyEventListener {
@Override @Override
public void propertySet(String property, Map params) { public void propertySet(String property, Map params)
{
if ( isEnabled() )
{
// Restart when configuration changed.
if (JIVEPROPERTY_PORT.equalsIgnoreCase( property ))
{
setEnabled( false );
setEnabled( true );
}
}
if(JIVEPROPERTY_PROXY_ENABLED.equalsIgnoreCase(property)) { if(JIVEPROPERTY_PROXY_ENABLED.equalsIgnoreCase(property)) {
Object value = params.get("value"); Object value = params.get("value");
boolean isEnabled = (value != null ? Boolean.parseBoolean(value.toString()) : boolean isEnabled = (value != null ? Boolean.parseBoolean(value.toString()) : DEFAULT_IS_PROXY_ENABLED);
DEFAULT_IS_PROXY_ENABLED);
setEnabled(isEnabled); setEnabled(isEnabled);
} }
} }
...@@ -429,6 +441,16 @@ public class FileTransferProxy extends BasicModule ...@@ -429,6 +441,16 @@ public class FileTransferProxy extends BasicModule
if(JIVEPROPERTY_PROXY_ENABLED.equalsIgnoreCase(property)) { if(JIVEPROPERTY_PROXY_ENABLED.equalsIgnoreCase(property)) {
setEnabled(DEFAULT_IS_PROXY_ENABLED); setEnabled(DEFAULT_IS_PROXY_ENABLED);
} }
if ( isEnabled() )
{
// Restart when configuration changed.
if (JIVEPROPERTY_PORT.equalsIgnoreCase( property ) )
{
setEnabled( false );
setEnabled( true );
}
}
} }
@Override @Override
......
...@@ -22,65 +22,89 @@ ...@@ -22,65 +22,89 @@
<%@ page import="org.jivesoftware.openfire.filetransfer.proxy.FileTransferProxy" %> <%@ page import="org.jivesoftware.openfire.filetransfer.proxy.FileTransferProxy" %>
<%@ page import="java.util.HashMap" %> <%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Map" %> <%@ page import="java.util.Map" %>
<%@ page import="org.jivesoftware.openfire.XMPPServer"%> <%@ page import="org.jivesoftware.openfire.XMPPServer" %>
<%@ page import="org.jivesoftware.util.JiveGlobals" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<jsp:useBean id="webManager" class="org.jivesoftware.util.WebManager"/> <jsp:useBean id="webManager" class="org.jivesoftware.util.WebManager"/>
<% webManager.init(request, response, session, application, out); %> <% webManager.init( request, response, session, application, out ); %>
<% <%
Map<String, String> errors = new HashMap<String, String>(); Map<String, String> errors = new HashMap<String, String>();
FileTransferProxy transferProxy = XMPPServer.getInstance().getFileTransferProxy(); FileTransferProxy transferProxy = XMPPServer.getInstance().getFileTransferProxy();
boolean isUpdated = request.getParameter("update") != null; boolean isUpdated = request.getParameter( "update" ) != null;
boolean isProxyEnabled = ParamUtils.getBooleanParameter(request, "proxyEnabled"); boolean isProxyEnabled = ParamUtils.getBooleanParameter( request, "proxyEnabled" );
int port = ParamUtils.getIntParameter(request, "port", 0); String hardcodedAddress = ParamUtils.getParameter( request, "hardcodedAddress" );
Cookie csrfCookie = CookieUtils.getCookie(request, "csrf"); int port = ParamUtils.getIntParameter( request, "port", 0 );
String csrfParam = ParamUtils.getParameter(request, "csrf"); Cookie csrfCookie = CookieUtils.getCookie( request, "csrf" );
String csrfParam = ParamUtils.getParameter( request, "csrf" );
if (isUpdated) { if ( isUpdated )
if (csrfCookie == null || csrfParam == null || !csrfCookie.getValue().equals(csrfParam)) { {
if ( csrfCookie == null || csrfParam == null || !csrfCookie.getValue().equals( csrfParam ) )
{
isUpdated = false; isUpdated = false;
errors.put("csrf", "CSRF Failure!"); errors.put( "csrf", "CSRF Failure!" );
} }
} }
csrfParam = StringUtils.randomString(15); csrfParam = StringUtils.randomString( 15 );
CookieUtils.setCookie(request, response, "csrf", csrfParam, -1); CookieUtils.setCookie( request, response, "csrf", csrfParam, -1 );
pageContext.setAttribute("csrf", csrfParam); pageContext.setAttribute( "csrf", csrfParam );
if (isUpdated) { if ( isUpdated )
if (isProxyEnabled) { {
if (port <= 0) { if ( hardcodedAddress != null && !hardcodedAddress.matches( "^[A-Za-z0-9-\\.]+$" ) )
errors.put("port", ""); {
} errors.put( "address", "" );
}
if ( port <= 0 )
{
errors.put( "port", "" );
} }
if (errors.isEmpty()) { if ( errors.isEmpty() )
if (isProxyEnabled) { {
transferProxy.setProxyPort(port); JiveGlobals.setProperty( "xmpp.proxy.externalip", hardcodedAddress );
if ( isProxyEnabled )
{
transferProxy.setProxyPort( port );
} }
transferProxy.enableFileTransferProxy(isProxyEnabled); transferProxy.enableFileTransferProxy( isProxyEnabled );
// Log the event // Log the event
webManager.logEvent("edited file transfer proxy settings", "port = "+port+"\nenabled = "+isProxyEnabled); webManager.logEvent( "edited file transfer proxy settings", "port = " + port + "\nhardcodedAddress = " + hardcodedAddress + "\nenabled = " + isProxyEnabled );
} }
} }
if (errors.isEmpty()) { if ( errors.isEmpty() )
isProxyEnabled = transferProxy.isProxyEnabled(); {
port = transferProxy.getProxyPort(); port = transferProxy.getProxyPort();
} }
else { else
if (port == 0) { {
isUpdated = false;
if ( port == 0 )
{
port = transferProxy.getProxyPort(); port = transferProxy.getProxyPort();
} }
} }
pageContext.setAttribute( "errors", errors );
pageContext.setAttribute( "isUpdated", isUpdated );
pageContext.setAttribute( "port", port );
pageContext.setAttribute( "hardcodedAddress", JiveGlobals.getProperty( "xmpp.proxy.externalip" ) );
pageContext.setAttribute( "fileTransferProxy", XMPPServer.getInstance().getFileTransferProxy() );
%> %>
<html> <html>
<head> <head>
<title><fmt:message key="filetransferproxy.settings.title"/></title> <title><fmt:message key="filetransferproxy.settings.title"/></title>
</head> </head>
<meta name="pageID" content="server-transfer-proxy"/> <meta name="pageID" content="server-transfer-proxy"/>
<body> <body>
...@@ -89,83 +113,75 @@ ...@@ -89,83 +113,75 @@
<fmt:message key="filetransferproxy.settings.info"/> <fmt:message key="filetransferproxy.settings.info"/>
</p> </p>
<% if (!errors.isEmpty()) { %> <c:forEach var="err" items="${errors}" varStatus="varStatus">
<div class="jive-error"> <admin:infobox type="error">
<table cellpadding="0" cellspacing="0" border="0"> <c:choose>
<tbody> <c:when test="${err.key eq 'port'}">
<tr> <fmt:message key="filetransferproxy.settings.valid.port"/>
<td class="jive-icon"><img alt="error" src="images/error-16x16.gif" width="16" height="16" </c:when>
border="0"/></td>
<td class="jive-icon-label">
<% if (errors.get("port") != null) { %>
<fmt:message key="filetransferproxy.settings.valid.port"/>
<% } %>
</td>
</tr>
</tbody>
</table>
</div>
<br>
<% }
else if (isUpdated) { %>
<div class="jive-success">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr><td class="jive-icon"><img alt="Success" src="images/success-16x16.gif" width="16" height="16"
border="0"></td>
<td class="jive-icon-label">
<fmt:message key="filetransferproxy.settings.confirm.updated"/>
</td></tr>
</tbody>
</table>
</div><br>
<% }
else { %>
<br>
<% } %>
<c:otherwise>
<c:if test="${not empty err.value}">
<fmt:message key="admin.error"/>: <c:out value="${err.value}"/>
</c:if>
(<c:out value="${err.key}"/>)
</c:otherwise>
</c:choose>
</admin:infobox>
<c:if test="${not varStatus.last}">
<br/>
</c:if>
</c:forEach>
<c:if test="${isUpdated}">
<admin:infobox type="success">
<fmt:message key="filetransferproxy.settings.confirm.updated"/>
</admin:infobox>
</c:if>
<br/>
<!-- BEGIN 'Proxy Service' --> <!-- BEGIN 'Proxy Service' -->
<c:set var="title"><fmt:message key="filetransferproxy.settings.enabled.legend"/></c:set>
<form action="file-transfer-proxy.jsp" method="post"> <form action="file-transfer-proxy.jsp" method="post">
<input type="hidden" name="csrf" value="${csrf}"> <input type="hidden" name="csrf" value="${csrf}">
<div class="jive-contentBoxHeader"> <admin:contentBox title="${title}">
<fmt:message key="filetransferproxy.settings.enabled.legend"/> <table cellpadding="3" cellspacing="0" border="0">
</div> <tbody>
<div class="jive-contentBox"> <tr valign="top">
<table cellpadding="3" cellspacing="0" border="0"> <td width="1%" nowrap>
<tbody> <input type="radio" name="proxyEnabled" value="true" id="rb02" ${fileTransferProxy.proxyEnabled ? 'checked' : ''}>
<tr valign="middle"> </td>
<td width="1%" nowrap> <td width="99%">
<input type="radio" name="proxyEnabled" value="true" id="rb02" <label for="rb02"><b><fmt:message key="filetransferproxy.settings.label_enable"/></b>- <fmt:message key="filetransferproxy.settings.label_enable_info"/></label>
<%= (isProxyEnabled ? "checked" : "") %> > <table border="0">
</td> <tr>
<td width="99%"> <td><label for="port"><fmt:message key="filetransferproxy.settings.label_port"/></label></td>
<label for="rb02"> <td><input type="text" size="5" maxlength="10" id="port" name="port" value="${port}"></td>
<b><fmt:message key="filetransferproxy.settings.label_enable"/></b> </tr>
- <fmt:message key="filetransferproxy.settings.label_enable_info"/> <tr>
</label> <input type="text" size="5" maxlength="10" name="port" <td><label for="hardcodedAddress"><fmt:message key="filetransferproxy.settings.label_hardcoded_address"/></label></td>
value="<%= port %>" > <td>
</td> <input type="text" size="40" maxlength="255" id="hardcodedAddress" name="hardcodedAddress" value="${hardcodedAddress}"> <fmt:message key="filetransferproxy.settings.label_hardcoded_optionality"/>
</tr> </td>
<tr valign="middle"> </tr>
<td width="1%" nowrap> </table>
<input type="radio" name="proxyEnabled" value="false" id="rb01" </td>
<%= (!isProxyEnabled ? "checked" : "") %> > </tr>
</td> <tr valign="middle">
<td width="99%"> <td width="1%" nowrap>
<label for="rb01"> <input type="radio" name="proxyEnabled" value="false" id="rb01" ${fileTransferProxy.proxyEnabled ? '' : 'checked'}>
<b><fmt:message key="filetransferproxy.settings.label_disable"/></b> </td>
- <fmt:message key="filetransferproxy.settings.label_disable_info"/> <td width="99%">
</label> <label for="rb01"><b><fmt:message key="filetransferproxy.settings.label_disable"/></b> - <fmt:message key="filetransferproxy.settings.label_disable_info"/></label>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </admin:contentBox>
<input type="submit" name="update" value="<fmt:message key="global.save_settings" />"> <input type="submit" name="update" value="<fmt:message key="global.save_settings" />">
</form> </form>
<!-- END 'Proxy Service' --> <!-- END 'Proxy Service' -->
</body> </body>
</html> </html>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment