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
......
...@@ -64,7 +64,7 @@ public class FileTransferProxy extends BasicModule ...@@ -64,7 +64,7 @@ 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( "host", externalIP );
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( "jid", getServiceDomain() );
response.addAttribute( "host", address.getHostAddress() ); response.addAttribute( "host", address.getHostAddress() );
response.addAttribute( "port", String.valueOf( connectionManager.getProxyPort() ) ); response.addAttribute( "port", String.valueOf( connectionManager.getProxyPort() ) );
} }
}
router.route(reply); router.route(reply);
return true; return true;
} }
...@@ -179,60 +197,44 @@ public class FileTransferProxy extends BasicModule ...@@ -179,60 +197,44 @@ 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));
final String interfaceName = JiveGlobals.getXMLProperty( "network.interface" );
if ( hardCodedProxyIP != null && !hardCodedProxyIP.trim().isEmpty() )
{
// First choice: a hardcoded IP address, if one exists.
try
{
bindInterface = InetAddress.getByName( hardCodedProxyIP.trim() );
} }
catch ( UnknownHostException e )
/**
* Returns the IP address(es) that the proxy connection manager is servicing.
*/
private Set<InetAddress> getAddresses()
{ {
Log.error( "Error binding to xmpp.proxy.externalip '{}'", interfaceName, e ); final String interfaceName = JiveGlobals.getXMLProperty( "network.interface" );
}
} final Set<InetAddress> result = new HashSet<>();
else if ( interfaceName != null && !interfaceName.trim().isEmpty() )
// Let's see if we hardcoded a specific interface, then use its address.
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.
*/
private Set<InetAddress> getAddresses()
{
final Set<InetAddress> result = new HashSet<>();
if ( bindInterface != null )
{
result.add( bindInterface );
}
else
{
// When there's no specific address configured, return all available addresses.
try try
{ {
final Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); final Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while ( networkInterfaces.hasMoreElements() ) while ( networkInterfaces.hasMoreElements() )
{ {
final NetworkInterface networkInterface = networkInterfaces.nextElement(); final NetworkInterface networkInterface = networkInterfaces.nextElement();
if ( networkInterface.isLoopback() )
{
continue;
}
final Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses(); final Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
while ( inetAddresses.hasMoreElements() ) while ( inetAddresses.hasMoreElements() )
{ {
...@@ -244,7 +246,6 @@ public class FileTransferProxy extends BasicModule ...@@ -244,7 +246,6 @@ public class FileTransferProxy extends BasicModule
{ {
Log.error( "Error determining all addresses for this server", e ); Log.error( "Error determining all addresses for this server", e );
} }
}
return result; return result;
} }
...@@ -292,6 +293,7 @@ public class FileTransferProxy extends BasicModule ...@@ -292,6 +293,7 @@ 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) {
...@@ -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>
<td class="jive-icon"><img alt="error" src="images/error-16x16.gif" width="16" height="16"
border="0"/></td>
<td class="jive-icon-label">
<% if (errors.get("port") != null) { %>
<fmt:message key="filetransferproxy.settings.valid.port"/> <fmt:message key="filetransferproxy.settings.valid.port"/>
<% } %> </c:when>
</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> <c:otherwise>
</tbody> <c:if test="${not empty err.value}">
</table> <fmt:message key="admin.error"/>: <c:out value="${err.value}"/>
</div><br> </c:if>
<% } (<c:out value="${err.key}"/>)
else { %> </c:otherwise>
<br> </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"/>
</div>
<div class="jive-contentBox">
<table cellpadding="3" cellspacing="0" border="0"> <table cellpadding="3" cellspacing="0" border="0">
<tbody> <tbody>
<tr valign="middle"> <tr valign="top">
<td width="1%" nowrap> <td width="1%" nowrap>
<input type="radio" name="proxyEnabled" value="true" id="rb02" <input type="radio" name="proxyEnabled" value="true" id="rb02" ${fileTransferProxy.proxyEnabled ? 'checked' : ''}>
<%= (isProxyEnabled ? "checked" : "") %> >
</td> </td>
<td width="99%"> <td width="99%">
<label for="rb02"> <label for="rb02"><b><fmt:message key="filetransferproxy.settings.label_enable"/></b>- <fmt:message key="filetransferproxy.settings.label_enable_info"/></label>
<b><fmt:message key="filetransferproxy.settings.label_enable"/></b> <table border="0">
- <fmt:message key="filetransferproxy.settings.label_enable_info"/> <tr>
</label> <input type="text" size="5" maxlength="10" name="port" <td><label for="port"><fmt:message key="filetransferproxy.settings.label_port"/></label></td>
value="<%= port %>" > <td><input type="text" size="5" maxlength="10" id="port" name="port" value="${port}"></td>
</tr>
<tr>
<td><label for="hardcodedAddress"><fmt:message key="filetransferproxy.settings.label_hardcoded_address"/></label></td>
<td>
<input type="text" size="40" maxlength="255" id="hardcodedAddress" name="hardcodedAddress" value="${hardcodedAddress}"> <fmt:message key="filetransferproxy.settings.label_hardcoded_optionality"/>
</td>
</tr>
</table>
</td> </td>
</tr> </tr>
<tr valign="middle"> <tr valign="middle">
<td width="1%" nowrap> <td width="1%" nowrap>
<input type="radio" name="proxyEnabled" value="false" id="rb01" <input type="radio" name="proxyEnabled" value="false" id="rb01" ${fileTransferProxy.proxyEnabled ? '' : 'checked'}>
<%= (!isProxyEnabled ? "checked" : "") %> >
</td> </td>
<td width="99%"> <td width="99%">
<label for="rb01"> <label for="rb01"><b><fmt:message key="filetransferproxy.settings.label_disable"/></b> - <fmt:message key="filetransferproxy.settings.label_disable_info"/></label>
<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