blob: fa3ce38f3974445ced39a9bc65c75db3b5569b6d [file] [log] [blame]
/*
* Copyright 1999-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ajp;
import java.io.IOException;
import java.security.MessageDigest;
import org.apache.tomcat.util.buf.HexUtils;
import org.apache.tomcat.util.http.BaseRequest;
/**
* Handler for the protocol negotiation. It will authenticate and
* exchange information about supported messages on each end.
*
*
* @author Henri Gomez [hgomez@apache.org]
* @author Dan Milstein [danmil@shore.net]
* @author Keith Wannamaker [Keith@Wannamaker.org]
* @author Costin Manolache
*/
public class NegociationHandler extends AjpHandler
{
// Initial Login Phase (web server -> servlet engine)
public static final byte JK_AJP14_LOGINIT_CMD=0x10;
// Second Login Phase (servlet engine -> web server), md5 seed is received
public static final byte JK_AJP14_LOGSEED_CMD=0x11;
// Third Login Phase (web server -> servlet engine),
// md5 of seed + secret is sent
public static final byte JK_AJP14_LOGCOMP_CMD=0x12;
// Login Accepted (servlet engine -> web server)
public static final byte JK_AJP14_LOGOK_CMD=0x13;
// Login Rejected (servlet engine -> web server), will be logged
public static final byte JK_AJP14_LOGNOK_CMD=0x14;
// Context Query (web server -> servlet engine),
// which URI are handled by servlet engine ?
public static final byte JK_AJP14_CONTEXT_QRY_CMD=0x15;
// Context Info (servlet engine -> web server), URI handled response
public static final byte JK_AJP14_CONTEXT_INFO_CMD= 0x16;
// Context Update (servlet engine -> web server), status of context changed
public static final byte JK_AJP14_CONTEXT_UPDATE_CMD= 0x17;
// Servlet Engine Status (web server -> servlet engine),
// what's the status of the servlet engine ?
public static final byte JK_AJP14_STATUS_CMD= 0x18;
// Secure Shutdown command (web server -> servlet engine),
//please servlet stop yourself.
public static final byte JK_AJP14_SHUTDOWN_CMD= 0x19;
// Secure Shutdown command Accepted (servlet engine -> web server)
public static final byte JK_AJP14_SHUTOK_CMD= 0x1A;
// Secure Shutdown Rejected (servlet engine -> web server)
public static final byte JK_AJP14_SHUTNOK_CMD= 0x1B;
// Context Status (web server -> servlet engine),
//what's the status of the context ?
public static final byte JK_AJP14_CONTEXT_STATE_CMD= 0x1C;
// Context Status Reply (servlet engine -> web server), status of context
public static final byte JK_AJP14_CONTEXT_STATE_REP_CMD = 0x1D;
// Unknown Packet Reply (web server <-> servlet engine),
//when a packet couldn't be decoded
public static final byte JK_AJP14_UNKNOW_PACKET_CMD = 0x1E;
// -------------------- Other constants --------------------
// Entropy Packet Size
public static final int AJP14_ENTROPY_SEED_LEN= 32;
public static final int AJP14_COMPUTED_KEY_LEN= 32;
// web-server want context info after login
public static final int AJP14_CONTEXT_INFO_NEG= 0x80000000;
// web-server want context updates
public static final int AJP14_CONTEXT_UPDATE_NEG= 0x40000000;
// web-server want compressed stream
public static final int AJP14_GZIP_STREAM_NEG= 0x20000000;
// web-server want crypted DES56 stream with secret key
public static final int AJP14_DES56_STREAM_NEG= 0x10000000;
// Extended info on server SSL vars
public static final int AJP14_SSL_VSERVER_NEG= 0x08000000;
// Extended info on client SSL vars
public static final int AJP14_SSL_VCLIENT_NEG= 0x04000000;
// Extended info on crypto SSL vars
public static final int AJP14_SSL_VCRYPTO_NEG= 0x02000000;
// Extended info on misc SSL vars
public static final int AJP14_SSL_VMISC_NEG= 0x01000000;
// mask of protocol supported
public static final int AJP14_PROTO_SUPPORT_AJPXX_NEG=0x00FF0000;
// communication could use AJP14
public static final int AJP14_PROTO_SUPPORT_AJP14_NEG=0x00010000;
// communication could use AJP15
public static final int AJP14_PROTO_SUPPORT_AJP15_NEG=0x00020000;
// communication could use AJP16
public static final int AJP14_PROTO_SUPPORT_AJP16_NEG=0x00040000;
// Some failure codes
public static final int AJP14_BAD_KEY_ERR= 0xFFFFFFFF;
public static final int AJP14_ENGINE_DOWN_ERR = 0xFFFFFFFE;
public static final int AJP14_RETRY_LATER_ERR = 0xFFFFFFFD;
public static final int AJP14_SHUT_AUTHOR_FAILED_ERR = 0xFFFFFFFC;
// Some status codes
public static final byte AJP14_CONTEXT_DOWN= 0x01;
public static final byte AJP14_CONTEXT_UP= 0x02;
public static final byte AJP14_CONTEXT_OK= 0x03;
// -------------------- Parameters --------------------
String containerSignature="Ajp14-based container";
String seed="seed";// will use random
String password;
int webserverNegociation=0;
// String webserverName;
public NegociationHandler() {
setSeed("myveryrandomentropy");
setPassword("myverysecretkey");
}
public void setContainerSignature( String s ) {
containerSignature=s;
}
// -------------------- State --------------------
// public String getWebserverName() {
// return webserverName;
// }
// -------------------- Parameters --------------------
/**
* Set the original entropy seed
*/
public void setSeed(String pseed)
{
String[] credentials = new String[1];
credentials[0] = pseed;
seed = digest(credentials, "md5");
}
/**
* Get the original entropy seed
*/
public String getSeed()
{
return seed;
}
/**
* Set the secret password
*/
public void setPassword(String ppwd)
{
password = ppwd;
}
/**
* Get the secret password
*/
public String getPassword()
{
return password;
}
// -------------------- Initialization --------------------
public void init( Ajp13 ajp14 ) {
super.init(ajp14);
// register incoming message handlers
ajp14.registerMessageType( JK_AJP14_LOGINIT_CMD,"JK_AJP14_LOGINIT_CMD",
this, null); //
ajp14.registerMessageType( JK_AJP14_LOGCOMP_CMD,"JK_AJP14_LOGCOMP_CMD",
this, null); //
ajp14.registerMessageType( RequestHandler.JK_AJP13_SHUTDOWN,"JK_AJP13_SHUTDOWN",
this, null); //
ajp14.registerMessageType( JK_AJP14_CONTEXT_QRY_CMD,
"JK_AJP14_CONTEXT_QRY_CMD",
this, null); //
ajp14.registerMessageType( JK_AJP14_STATUS_CMD,"JK_AJP14_STATUS_CMD",
this, null); //
ajp14.registerMessageType( JK_AJP14_SHUTDOWN_CMD,
"JK_AJP14_SHUTDOWN_CMD",
this, null); //
ajp14.registerMessageType( JK_AJP14_CONTEXT_STATE_CMD,
"JK_AJP14_CONTEXT_STATE_CMD",
this, null); //
ajp14.registerMessageType( JK_AJP14_UNKNOW_PACKET_CMD,
"JK_AJP14_UNKNOW_PACKET_CMD",
this, null); //
// register outgoing messages handler
ajp14.registerMessageType( JK_AJP14_LOGNOK_CMD,"JK_AJP14_LOGNOK_CMD",
this,null );
}
// -------------------- Dispatch --------------------
public int handleAjpMessage( int type, Ajp13 ch, Ajp13Packet hBuf,
BaseRequest req )
throws IOException
{
System.out.println("handleAjpMessage: " + type );
Ajp13Packet outBuf=ch.outBuf;
// Valid requests when not logged:
switch( type ) {
case JK_AJP14_LOGINIT_CMD :
return handleLogInit(ch, hBuf, outBuf);
case JK_AJP14_LOGCOMP_CMD :
return handleLogComp(ch, hBuf, outBuf);
case RequestHandler.JK_AJP13_SHUTDOWN:
return -2;
case JK_AJP14_CONTEXT_QRY_CMD :
return handleContextQuery(ch, hBuf, outBuf);
case JK_AJP14_STATUS_CMD :
return handleStatus(hBuf, outBuf);
case JK_AJP14_SHUTDOWN_CMD :
return handleShutdown(hBuf, outBuf);
case JK_AJP14_CONTEXT_STATE_CMD :
return handleContextState(hBuf, outBuf);
case JK_AJP14_UNKNOW_PACKET_CMD :
return handleUnknowPacket(hBuf, outBuf);
default:
log("unknown command " + type + " received");
return 200; // XXX This is actually an error condition
}
//return UNKNOWN;
}
//----------- Implementation for various protocol commands -----------
/**
* Handle the Initial Login Message from Web-Server
*
* Get the requested Negociation Flags
* Get also the Web-Server Name
*
* Send Login Seed (MD5 of seed)
*/
private int handleLogInit( Ajp13 ch, Ajp13Packet msg,
Ajp13Packet outBuf )
throws IOException
{
webserverNegociation = msg.getLongInt();
String webserverName = msg.getString();
log("in handleLogInit with nego " +
decodeNegociation(webserverNegociation) +
" from webserver " + webserverName);
outBuf.reset();
outBuf.appendByte(JK_AJP14_LOGSEED_CMD);
String[] credentials = new String[1];
credentials[0] = getSeed();
outBuf.appendXBytes(getSeed().getBytes(), 0, AJP14_ENTROPY_SEED_LEN);
log("in handleLogInit: sent entropy " + getSeed());
outBuf.end();
ch.send(outBuf);
return 304;
}
/**
* Handle the Second Phase of Login (accreditation)
*
* Get the MD5 digest of entropy + secret password
* If the authentification is valid send back LogOk
* If the authentification failed send back LogNok
*/
private int handleLogComp( Ajp13 ch, Ajp13Packet msg,
Ajp13Packet outBuf )
throws IOException
{
// log("in handleLogComp :");
byte [] rdigest = new byte[AJP14_ENTROPY_SEED_LEN];
if (msg.getXBytes(rdigest, AJP14_ENTROPY_SEED_LEN) < 0)
return 200;
String[] credentials = new String[2];
credentials[0] = getSeed();
credentials[1] = getPassword();
String computed = digest(credentials, "md5");
String received = new String(rdigest);
// XXX temp workaround, to test the rest of the connector.
if ( ! computed.equalsIgnoreCase(received)) {
log("in handleLogComp : authentification failure received=" +
received + " awaited=" + computed);
}
if (false ) { // ! computed.equalsIgnoreCase(received)) {
log("in handleLogComp : authentification failure received=" +
received + " awaited=" + computed);
// we should have here a security mecanism which could maintain
// a list of remote IP which failed too many times
// so we could reject them quickly at next connect
outBuf.reset();
outBuf.appendByte(JK_AJP14_LOGNOK_CMD);
outBuf.appendLongInt(AJP14_BAD_KEY_ERR);
outBuf.end();
ch.send(outBuf);
return 200;
} else {
// logged we can go process requests
channel.setLogged(true);
outBuf.reset();
outBuf.appendByte(JK_AJP14_LOGOK_CMD);
outBuf.appendLongInt(getProtocolFlags(webserverNegociation));
outBuf.appendString( containerSignature );
outBuf.end();
ch.send(outBuf);
}
return (304);
}
private int handleContextQuery( Ajp13 ch, Ajp13Packet msg,
Ajp13Packet outBuf )
throws IOException
{
log("in handleContextQuery :");
String virtualHost = msg.getString();
log("in handleContextQuery for virtual" + virtualHost);
outBuf.reset();
outBuf.appendByte(JK_AJP14_CONTEXT_INFO_CMD);
outBuf.appendString( virtualHost );
log("in handleContextQuery for virtual " + virtualHost +
"examples URI/MIMES");
outBuf.appendString("examples"); // first context - examples
outBuf.appendString("servlet/*"); // examples/servlet/*
outBuf.appendString("*.jsp"); // examples/*.jsp
outBuf.appendString(""); // no more URI/MIMES
log("in handleContextQuery for virtual " + virtualHost +
"send admin URI/MIMES");
outBuf.appendString("admin"); // second context - admin
outBuf.appendString("servlet/*"); // /admin//servlet/*
outBuf.appendString("*.jsp"); // /admin/*.jsp
outBuf.appendString(""); // no more URI/MIMES
outBuf.appendString(""); // no more contexts
outBuf.end();
ch.send(outBuf);
return (304);
}
private int handleStatus( Ajp13Packet msg, Ajp13Packet outBuf )
throws IOException
{
log("in handleStatus :");
return (304);
}
private int handleShutdown( Ajp13Packet msg, Ajp13Packet outBuf )
throws IOException
{
log("in handleShutdown :");
return (304);
}
private int handleContextState( Ajp13Packet msg , Ajp13Packet outBuf)
throws IOException
{
log("in handleContextState :");
return (304);
}
private int handleUnknowPacket( Ajp13Packet msg, Ajp13Packet outBuf )
throws IOException
{
log("in handleUnknowPacket :");
return (304);
}
// -------------------- Utils --------------------
/**
* Compute the Protocol Negociation Flags
*
* Depending the protocol fatures implemented on servet-engine,
* we'll drop requested features which could be asked by web-server
*
* Hopefully this functions could be overrided by decendants
*/
private int getProtocolFlags(int wanted)
{
// no real-time context update
wanted &= ~(AJP14_CONTEXT_UPDATE_NEG |
// no gzip compression yet
AJP14_GZIP_STREAM_NEG |
// no DES56 cyphering yet
AJP14_DES56_STREAM_NEG |
// no Extended info on server SSL vars yet
AJP14_SSL_VSERVER_NEG |
// no Extended info on client SSL vars yet
AJP14_SSL_VCLIENT_NEG |
// no Extended info on crypto SSL vars yet
AJP14_SSL_VCRYPTO_NEG |
// no Extended info on misc SSL vars yet
AJP14_SSL_VMISC_NEG |
// Reset AJP protocol mask
AJP14_PROTO_SUPPORT_AJPXX_NEG);
// Only strict AJP14 supported
return (wanted | AJP14_PROTO_SUPPORT_AJP14_NEG);
}
/**
* Compute a digest (MD5 in AJP14) for an array of String
*/
public final String digest(String[] credentials, String algorithm) {
try {
// Obtain a new message digest with MD5 encryption
MessageDigest md =
(MessageDigest)MessageDigest.getInstance(algorithm).clone();
// encode the credentials items
for (int i = 0; i < credentials.length; i++) {
if( debug > 0 )
log("Credentials : " + i + " " + credentials[i]);
if( credentials[i] != null )
md.update(credentials[i].getBytes());
}
// obtain the byte array from the digest
byte[] dig = md.digest();
return HexUtils.convert(dig);
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
// -------------------- Debugging --------------------
// Very usefull for develoment
/**
* Display Negociation field in human form
*/
private String decodeNegociation(int nego)
{
StringBuffer buf = new StringBuffer(128);
if ((nego & AJP14_CONTEXT_INFO_NEG) != 0)
buf.append(" CONTEXT-INFO");
if ((nego & AJP14_CONTEXT_UPDATE_NEG) != 0)
buf.append(" CONTEXT-UPDATE");
if ((nego & AJP14_GZIP_STREAM_NEG) != 0)
buf.append(" GZIP-STREAM");
if ((nego & AJP14_DES56_STREAM_NEG) != 0)
buf.append(" DES56-STREAM");
if ((nego & AJP14_SSL_VSERVER_NEG) != 0)
buf.append(" SSL-VSERVER");
if ((nego & AJP14_SSL_VCLIENT_NEG) != 0)
buf.append(" SSL-VCLIENT");
if ((nego & AJP14_SSL_VCRYPTO_NEG) != 0)
buf.append(" SSL-VCRYPTO");
if ((nego & AJP14_SSL_VMISC_NEG) != 0)
buf.append(" SSL-VMISC");
if ((nego & AJP14_PROTO_SUPPORT_AJP14_NEG) != 0)
buf.append(" AJP14");
if ((nego & AJP14_PROTO_SUPPORT_AJP15_NEG) != 0)
buf.append(" AJP15");
if ((nego & AJP14_PROTO_SUPPORT_AJP16_NEG) != 0)
buf.append(" AJP16");
return (buf.toString());
}
private static int debug=10;
void log(String s) {
System.out.println("Ajp14Negotiation: " + s );
}
}