Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
Expand Down Expand Up @@ -106,6 +104,12 @@
* It parses the SOAP request, calls the appropriate class
* which performs the LDAP operation, and returns the response
* as a DSML response.
* <p>
* Everything is logged through {@code getServletContext().log()}: it is the
* only sink which survives at runtime, as
* {@code LDAPConnection.connectToHost()} turns {@code java.util.logging} off
* for the whole JVM on every non-verbose connection, and the war ships
* {@code slf4j-api} without any provider.
*/
public class DSMLServlet extends HttpServlet {
private static final String PKG_NAME = "org.opends.dsml.protocol";
Expand Down Expand Up @@ -165,6 +169,9 @@ public class DSMLServlet extends HttpServlet {
*/
@Override
public void init(ServletConfig config) throws ServletException {
// Let GenericServlet keep the configuration: getServletContext() relies on
// it, and it is the only logging facility available at runtime.
super.init(config);
try {
hostName = stringValue(config, HOST);
port = Integer.valueOf(stringValue(config, PORT));
Expand Down Expand Up @@ -333,7 +340,6 @@ public void doPost(HttpServletRequest req, HttpServletResponse res)
connOptions.setUseSSL(useSSL);
connOptions.setStartTLS(useStartTLS);

LDAPConnection connection = null;
BatchRequest batchRequest = null;

// Keep the Servlet input stream buffered in case the SOAP un-marshalling
Expand Down Expand Up @@ -400,9 +406,8 @@ else if (headerVal.startsWith(SOAPConstants.SOAP_1_2_CONTENT_TYPE))
messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL);
messageContentType = SOAPConstants.SOAP_1_2_CONTENT_TYPE;
}
else {
throw new ServletException("Content-Type does not match SOAP 1.1 or SOAP 1.2");
}
// An unsupported Content-Type leaves the message factory unset: the
// request is rejected as malformed once all the headers are read.
}
catch (SOAPException e)
{
Expand Down Expand Up @@ -430,12 +435,14 @@ else if (headerVal.startsWith(SOAPConstants.SOAP_1_2_CONTENT_TYPE))
bindPassword = unencoded.substring(colon + 1);
}
} catch (final LocalizedIllegalArgumentException ex) {
// user/DN:password parsing error
// user/DN:password parsing error. Keep reading the headers: the
// Content-Type may still be ahead, and it decides which SOAP
// version the error is reported with.
batchResponses.add(
createErrorResponse(objFactory,
new LDAPException(LDAPResultCode.INVALID_CREDENTIALS,
LocalizableMessage.raw(ex.getMessage()))));
break;
continue;
}
}
StringTokenizer tk = new StringTokenizer(headerVal, ",");
Expand Down Expand Up @@ -477,6 +484,31 @@ else if (headerVal.startsWith(SOAPConstants.SOAP_1_2_CONTENT_TYPE))
}
}

if ( messageFactory == null ) {
// The request carries no Content-Type header, or one which matches
// neither SOAP 1.1 nor SOAP 1.2: it cannot be parsed. Fall back to
// SOAP 1.1 for the response and reject the request as malformed,
// unless an error has already been reported.
try
{
messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL);
messageContentType = SOAPConstants.SOAP_1_1_CONTENT_TYPE;
}
catch (SOAPException e)
{
throw new ServletException(e.getMessage());
}
if ( batchResponses.isEmpty() ) {
// Nothing has been read from the stream yet, so the SAX pass can still
// recover the requestID and let the client correlate the reply.
batchResponses.add(
createXMLParsingErrorResponse(is,
objFactory,
batchResponse,
"Content-Type does not match SOAP 1.1 or SOAP 1.2"));
}
}

// if an error already occurred, the list is not empty
if ( batchResponses.isEmpty() ) {
try {
Expand Down Expand Up @@ -520,6 +552,13 @@ else if (headerVal.startsWith(SOAPConstants.SOAP_1_2_CONTENT_TYPE))
boolean authzInControl = false;
batchRequest = batchRequestElement.getValue();

// The connection options are shared by all the batch requests of this
// SOAP body, so the authzid of the previous one must not survive into
// the bind of this one: it would run under an authorization identity
// it never asked for, and addSASLProperty() appends to the values of
// a key, which SASL PLAIN rejects as a multi-valued authzid.
connOptions.getSASLProperties().remove("authzid");

/*
* Process optional authRequest (i.e. use authz)
*/
Expand All @@ -541,10 +580,12 @@ else if (headerVal.startsWith(SOAPConstants.SOAP_1_2_CONTENT_TYPE))

boolean connected = false;

if ( connection == null ) {
connection = new LDAPConnection(hostName, port, connOptions);
// Each batch request gets its own connection: the previous one has
// been closed by the finally block below.
LDAPConnection connection =
new LDAPConnection(hostName, port, connOptions);
try {
try {

connection.connectToHost(bindDN, bindPassword);
if (authzInControl)
{
Expand All @@ -565,35 +606,37 @@ else if (headerVal.startsWith(SOAPConstants.SOAP_1_2_CONTENT_TYPE))
// if connection failed, return appropriate error response
batchResponses.add(createErrorResponse(objFactory, e));
}
}
if ( connected ) {
List<DsmlMessage> list = batchRequest.getBatchRequests();

for (DsmlMessage request : list) {
JAXBElement<?> result = performLDAPRequest(connection, objFactory, proxyAuthzControl, request);
if ( result != null ) {
batchResponses.add(result);
}
// evaluate response to check if an error occurred
Object o = result.getValue();
if ( o instanceof ErrorResponse ) {
if ( ON_ERROR_EXIT.equals(batchRequest.getOnError()) ) {
break;
if ( connected ) {
List<DsmlMessage> list = batchRequest.getBatchRequests();

for (DsmlMessage request : list) {
JAXBElement<?> result = performLDAPRequest(connection, objFactory, proxyAuthzControl, request);
if ( result == null ) {
// an abandon request does not produce any response element
continue;
}
} else if ( o instanceof LDAPResult ) {
int code = ((LDAPResult)o).getResultCode().getCode();
if ( code != LDAPResultCode.SUCCESS
&& code != LDAPResultCode.REFERRAL
&& code != LDAPResultCode.COMPARE_TRUE
&& code != LDAPResultCode.COMPARE_FALSE && ON_ERROR_EXIT.equals(batchRequest.getOnError()) )
{
break;
batchResponses.add(result);
// evaluate response to check if an error occurred
Object o = result.getValue();
if ( o instanceof ErrorResponse ) {
if ( ON_ERROR_EXIT.equals(batchRequest.getOnError()) ) {
break;
}
} else if ( o instanceof LDAPResult ) {
int code = ((LDAPResult)o).getResultCode().getCode();
if ( code != LDAPResultCode.SUCCESS
&& code != LDAPResultCode.REFERRAL
&& code != LDAPResultCode.COMPARE_TRUE
&& code != LDAPResultCode.COMPARE_FALSE && ON_ERROR_EXIT.equals(batchRequest.getOnError()) )
{
break;
}
}
}
}
}
// close connection to LDAP server
if ( connection != null ) {
} finally {
// close connection to LDAP server, whatever happened while
// processing the batch
connection.close(nextMessageID);
}
}
Expand All @@ -604,7 +647,8 @@ else if (headerVal.startsWith(SOAPConstants.SOAP_1_2_CONTENT_TYPE))
marshaller.marshal(objFactory.createBatchResponse(batchResponse), doc);
sendResponse(doc, messageFactory, messageContentType, res);
} catch (Exception e) {
e.printStackTrace();
// The client gets an empty response: at least make the cause visible.
getServletContext().log("Unable to send the DSML response", e);
}

}
Expand All @@ -628,14 +672,14 @@ private void safeSetFeature(XMLReader xmlReader, String feature, boolean flag)
{
if (logFeatureWarnings.compareAndSet(false, true))
{
Logger.getLogger(PKG_NAME).log(Level.SEVERE, "XMLReader unsupported feature " + feature);
getServletContext().log("XMLReader unsupported feature " + feature);
}
}
catch (SAXNotRecognizedException e)
{
if (logFeatureWarnings.compareAndSet(false, true))
{
Logger.getLogger(PKG_NAME).log(Level.SEVERE, "XMLReader unrecognized feature " + feature);
getServletContext().log("XMLReader unrecognized feature " + feature);
}
}
}
Expand Down Expand Up @@ -893,7 +937,7 @@ private void safeSetFeature(DocumentBuilderFactory factory, String feature, bool
catch (ParserConfigurationException e) {
if (logFeatureWarnings.compareAndSet(false, true))
{
Logger.getLogger(PKG_NAME).log(Level.SEVERE, "DocumentBuilderFactory unsupported feature " + feature);
getServletContext().log("DocumentBuilderFactory unsupported feature " + feature);
}
}
}
Expand All @@ -916,7 +960,7 @@ private Document createSafeDocument()
catch (ParserConfigurationException e)
{
if (logFeatureWarnings.compareAndSet(false, true)) {
Logger.getLogger(PKG_NAME).log(Level.SEVERE, "DocumentBuilderFactory cannot be configured securely");
getServletContext().log("DocumentBuilderFactory cannot be configured securely");
}
}
dbf.setXIncludeAware(false);
Expand Down
Loading
Loading