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
filetransferproxy.settings.label_disable_info=This server will not act as a file transfer proxy.
filetransferproxy.settings.label_enable=Enabled
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.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
......
......@@ -64,7 +64,7 @@ public class FileTransferProxy extends BasicModule
implements ServerItemsProvider, DiscoInfoProvider, DiscoItemsProvider,
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.
......@@ -77,6 +77,11 @@ public class FileTransferProxy extends BasicModule
*/
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.
*/
......@@ -132,15 +137,28 @@ public class FileTransferProxy extends BasicModule
else if (FileTransferManager.NAMESPACE_BYTESTREAMS.equals(namespace)) {
if (packet.getType() == IQ.Type.get) {
IQ reply = IQ.createResultIQ(packet);
Element newChild = reply.setChildElement("query",
FileTransferManager.NAMESPACE_BYTESTREAMS);
for ( InetAddress address : getAddresses() )
Element newChild = reply.setChildElement("query", FileTransferManager.NAMESPACE_BYTESTREAMS);
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( "host", address.getHostAddress() );
response.addAttribute( "port", String.valueOf( connectionManager.getProxyPort() ) );
}
}
router.route(reply);
return true;
}
......@@ -179,60 +197,44 @@ public class FileTransferProxy extends BasicModule
routingTable = server.getRoutingTable();
router = server.getPacketRouter();
final String hardCodedProxyIP = JiveGlobals.getProperty( "xmpp.proxy.externalip" );
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() );
connectionManager = new ProxyConnectionManager(getFileTransferManager(server));
}
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 );
}
}
else if ( interfaceName != null && !interfaceName.trim().isEmpty() )
final String interfaceName = JiveGlobals.getXMLProperty( "network.interface" );
final Set<InetAddress> result = new HashSet<>();
// 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
{
bindInterface = InetAddress.getByName( interfaceName.trim() );
result.add( bindInterface );
return result;
}
catch ( UnknownHostException 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));
}
/**
* 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.
// When there's no specific address configured, return all available (non-loopback) addresses.
try
{
final Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while ( networkInterfaces.hasMoreElements() )
{
final NetworkInterface networkInterface = networkInterfaces.nextElement();
if ( networkInterface.isLoopback() )
{
continue;
}
final Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
while ( inetAddresses.hasMoreElements() )
{
......@@ -244,7 +246,6 @@ public class FileTransferProxy extends BasicModule
{
Log.error( "Error determining all addresses for this server", e );
}
}
return result;
}
......@@ -292,6 +293,7 @@ public class FileTransferProxy extends BasicModule
public void enableFileTransferProxy(boolean isEnabled) {
JiveGlobals.setProperty(FileTransferProxy.JIVEPROPERTY_PROXY_ENABLED,
Boolean.toString(isEnabled));
setEnabled( isEnabled );
}
private void setEnabled(boolean isEnabled) {
......@@ -415,11 +417,21 @@ public class FileTransferProxy extends BasicModule
private class FileTransferPropertyListener implements PropertyEventListener {
@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)) {
Object value = params.get("value");
boolean isEnabled = (value != null ? Boolean.parseBoolean(value.toString()) :
DEFAULT_IS_PROXY_ENABLED);
boolean isEnabled = (value != null ? Boolean.parseBoolean(value.toString()) : DEFAULT_IS_PROXY_ENABLED);
setEnabled(isEnabled);
}
}
......@@ -429,6 +441,16 @@ public class FileTransferProxy extends BasicModule
if(JIVEPROPERTY_PROXY_ENABLED.equalsIgnoreCase(property)) {
setEnabled(DEFAULT_IS_PROXY_ENABLED);
}
if ( isEnabled() )
{
// Restart when configuration changed.
if (JIVEPROPERTY_PORT.equalsIgnoreCase( property ) )
{
setEnabled( false );
setEnabled( true );
}
}
}
@Override
......
......@@ -22,65 +22,89 @@
<%@ page import="org.jivesoftware.openfire.filetransfer.proxy.FileTransferProxy" %>
<%@ page import="java.util.HashMap" %>
<%@ 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/functions" prefix="fn" %>
<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>();
FileTransferProxy transferProxy = XMPPServer.getInstance().getFileTransferProxy();
boolean isUpdated = request.getParameter("update") != null;
boolean isProxyEnabled = ParamUtils.getBooleanParameter(request, "proxyEnabled");
int port = ParamUtils.getIntParameter(request, "port", 0);
Cookie csrfCookie = CookieUtils.getCookie(request, "csrf");
String csrfParam = ParamUtils.getParameter(request, "csrf");
boolean isUpdated = request.getParameter( "update" ) != null;
boolean isProxyEnabled = ParamUtils.getBooleanParameter( request, "proxyEnabled" );
String hardcodedAddress = ParamUtils.getParameter( request, "hardcodedAddress" );
int port = ParamUtils.getIntParameter( request, "port", 0 );
Cookie csrfCookie = CookieUtils.getCookie( request, "csrf" );
String csrfParam = ParamUtils.getParameter( request, "csrf" );
if (isUpdated) {
if (csrfCookie == null || csrfParam == null || !csrfCookie.getValue().equals(csrfParam)) {
if ( isUpdated )
{
if ( csrfCookie == null || csrfParam == null || !csrfCookie.getValue().equals( csrfParam ) )
{
isUpdated = false;
errors.put("csrf", "CSRF Failure!");
errors.put( "csrf", "CSRF Failure!" );
}
}
csrfParam = StringUtils.randomString(15);
CookieUtils.setCookie(request, response, "csrf", csrfParam, -1);
pageContext.setAttribute("csrf", csrfParam);
if (isUpdated) {
if (isProxyEnabled) {
if (port <= 0) {
errors.put("port", "");
csrfParam = StringUtils.randomString( 15 );
CookieUtils.setCookie( request, response, "csrf", csrfParam, -1 );
pageContext.setAttribute( "csrf", csrfParam );
if ( isUpdated )
{
if ( hardcodedAddress != null && !hardcodedAddress.matches( "^[A-Za-z0-9-\\.]+$" ) )
{
errors.put( "address", "" );
}
if ( port <= 0 )
{
errors.put( "port", "" );
}
if (errors.isEmpty()) {
if (isProxyEnabled) {
transferProxy.setProxyPort(port);
if ( errors.isEmpty() )
{
JiveGlobals.setProperty( "xmpp.proxy.externalip", hardcodedAddress );
if ( isProxyEnabled )
{
transferProxy.setProxyPort( port );
}
transferProxy.enableFileTransferProxy(isProxyEnabled);
transferProxy.enableFileTransferProxy( isProxyEnabled );
// 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()) {
isProxyEnabled = transferProxy.isProxyEnabled();
if ( errors.isEmpty() )
{
port = transferProxy.getProxyPort();
}
else {
if (port == 0) {
else
{
isUpdated = false;
if ( port == 0 )
{
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>
<head>
<title><fmt:message key="filetransferproxy.settings.title"/></title>
<title><fmt:message key="filetransferproxy.settings.title"/></title>
</head>
<meta name="pageID" content="server-transfer-proxy"/>
<body>
......@@ -89,83 +113,75 @@
<fmt:message key="filetransferproxy.settings.info"/>
</p>
<% if (!errors.isEmpty()) { %>
<div class="jive-error">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<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) { %>
<c:forEach var="err" items="${errors}" varStatus="varStatus">
<admin:infobox type="error">
<c:choose>
<c:when test="${err.key eq 'port'}">
<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"/>
</c:when>
</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' -->
<c:set var="title"><fmt:message key="filetransferproxy.settings.enabled.legend"/></c:set>
<form action="file-transfer-proxy.jsp" method="post">
<input type="hidden" name="csrf" value="${csrf}">
<div class="jive-contentBoxHeader">
<fmt:message key="filetransferproxy.settings.enabled.legend"/>
</div>
<div class="jive-contentBox">
<admin:contentBox title="${title}">
<table cellpadding="3" cellspacing="0" border="0">
<tbody>
<tr valign="middle">
<tr valign="top">
<td width="1%" nowrap>
<input type="radio" name="proxyEnabled" value="true" id="rb02"
<%= (isProxyEnabled ? "checked" : "") %> >
<input type="radio" name="proxyEnabled" value="true" id="rb02" ${fileTransferProxy.proxyEnabled ? 'checked' : ''}>
</td>
<td width="99%">
<label for="rb02">
<b><fmt:message key="filetransferproxy.settings.label_enable"/></b>
- <fmt:message key="filetransferproxy.settings.label_enable_info"/>
</label> <input type="text" size="5" maxlength="10" name="port"
value="<%= port %>" >
<label for="rb02"><b><fmt:message key="filetransferproxy.settings.label_enable"/></b>- <fmt:message key="filetransferproxy.settings.label_enable_info"/></label>
<table border="0">
<tr>
<td><label for="port"><fmt:message key="filetransferproxy.settings.label_port"/></label></td>
<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>
</tr>
<tr valign="middle">
<td width="1%" nowrap>
<input type="radio" name="proxyEnabled" value="false" id="rb01"
<%= (!isProxyEnabled ? "checked" : "") %> >
<input type="radio" name="proxyEnabled" value="false" id="rb01" ${fileTransferProxy.proxyEnabled ? '' : 'checked'}>
</td>
<td width="99%">
<label for="rb01">
<b><fmt:message key="filetransferproxy.settings.label_disable"/></b>
- <fmt:message key="filetransferproxy.settings.label_disable_info"/>
</label>
<label for="rb01"><b><fmt:message key="filetransferproxy.settings.label_disable"/></b> - <fmt:message key="filetransferproxy.settings.label_disable_info"/></label>
</td>
</tr>
</tbody>
</table>
</div>
</admin:contentBox>
<input type="submit" name="update" value="<fmt:message key="global.save_settings" />">
</form>
<!-- END 'Proxy Service' -->
</body>
</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