costin 01/12/18 14:16:37 Added: jk/java/org/apache/jk Ajp13.java Ajp13Packet.java AjpHandler.java NegociationHandler.java RequestHandler.java Log: Adding the files from org.apache.ajp. This is the same code, as startup point for jk2 and refactoring. Revision Changes Path 1.1 jakarta-tomcat-connectors/jk/java/org/apache/jk/Ajp13.java Index: Ajp13.java =================================================================== /* * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package org.apache.jk; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.io.InputStream; import java.io.OutputStream; import java.io.ByteArrayInputStream; import java.net.Socket; import java.util.Enumeration; import java.security.cert.X509Certificate; import java.security.cert.CertificateFactory; import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.http.MimeHeaders; import org.apache.tomcat.util.http.HttpMessages; import org.apache.tomcat.util.http.BaseRequest; /** * Represents a single, persistent connection between the web server and * the servlet container. Uses the Apache JServ Protocol version 1.3 for * communication. Because this protocal does not multiplex requests, this * connection can only be associated with a single request-handling cycle * at a time.<P> * * This class contains knowledge about how an individual packet is laid out * (via the <CODE>Ajp13Packet</CODE> class), and also about the * stages of communicaton between the server and the servlet container. It * translates from Tomcat's internal servlet support methods * (e.g. doWrite) to the correct packets to send to the web server. * * @see Ajp13Interceptor * * @author Dan Milstein [[EMAIL PROTECTED]] * @author Keith Wannamaker [[EMAIL PROTECTED]] * @author Kevin Seguin [[EMAIL PROTECTED]] * @author Henri Gomez [[EMAIL PROTECTED]] * @author Costin Manolache */ public class Ajp13 { public static final int MAX_PACKET_SIZE=8192; public static final int H_SIZE=4; // Size of basic packet header public static final int MAX_READ_SIZE = MAX_PACKET_SIZE - H_SIZE - 2; public static final int MAX_SEND_SIZE = MAX_PACKET_SIZE - H_SIZE - 4; // Prefix codes for message types from server to container public static final byte JK_AJP13_SHUTDOWN = 7; // Error code for Ajp13 public static final int JK_AJP13_BAD_HEADER = -100; public static final int JK_AJP13_NO_HEADER = -101; public static final int JK_AJP13_COMM_CLOSED = -102; public static final int JK_AJP13_COMM_BROKEN = -103; public static final int JK_AJP13_BAD_BODY = -104; public static final int JK_AJP13_INCOMPLETE_BODY = -105; // ============ Instance Properties ==================== OutputStream out; InputStream in; // Buffer used of output body and headers public Ajp13Packet outBuf = new Ajp13Packet( MAX_PACKET_SIZE ); // Buffer used for input body Ajp13Packet inBuf = new Ajp13Packet( MAX_PACKET_SIZE ); // Buffer used for request head ( and headers ) Ajp13Packet hBuf=new Ajp13Packet( MAX_PACKET_SIZE ); // Holds incoming reads of request body data (*not* header data) byte []bodyBuff = new byte[MAX_READ_SIZE]; int blen; // Length of current chunk of body data in buffer int pos; // Current read position within that buffer boolean end_of_stream; // true if we've received an empty packet // Required handler - essential request processing public RequestHandler reqHandler; // AJP14 - detect protocol,set communication parameters, login // If no password is set, use only Ajp13 messaging boolean backwardCompat=true; boolean logged=false; public Ajp13() { super(); initBuf(); reqHandler=new RequestHandler(); reqHandler.init( this ); } public Ajp13(RequestHandler reqHandler ) { super(); initBuf(); this.reqHandler=reqHandler; reqHandler.init( this ); } /** Will be overriden */ public void initBuf() { outBuf = new Ajp13Packet( MAX_PACKET_SIZE ); inBuf = new Ajp13Packet( MAX_PACKET_SIZE ); hBuf=new Ajp13Packet( MAX_PACKET_SIZE ); } public void recycle() { if (debug > 0) { logger.log("recycle()"); } // This is a touch cargo-cultish, but I think wise. blen = 0; pos = 0; end_of_stream = false; logged=false; } /** * Associate an open socket with this instance. */ public void setSocket( Socket socket ) throws IOException { if (debug > 0) { logger.log("setSocket()"); } socket.setSoLinger( true, 100); out = socket.getOutputStream(); in = socket.getInputStream(); pos = 0; } /** * Backward compat mode, no login needed */ public void setBackward(boolean b) { backwardCompat=b; } public boolean isLogged() { return logged; } void setLogged( boolean b ) { logged=b; } // -------------------- Handlers registry -------------------- static final int MAX_HANDLERS=32; static final int RESERVED=16; // reserved names, backward compat // Note that we don't make distinction between in and out // messages ( i.e. one id is used only in one direction ) AjpHandler handlers[]=new AjpHandler[MAX_HANDLERS]; String handlerName[]=new String[MAX_HANDLERS]; int currentId=RESERVED; public int registerMessageType( int id, String name, AjpHandler h, String sig[] ) { if( id < 0 ) { // try to find it by name for( int i=0; i< handlerName.length; i++ ) if( name.equals( handlerName[i] ) ) return i; handlerName[currentId]=name; handlers[currentId]=h; currentId++; return currentId; } // fixed id handlerName[id]=name; handlers[id]=h; return id; } // -------------------- Main dispatch -------------------- /** * Read a new packet from the web server and decode it. If it's a * forwarded request, store its properties in the passed-in AjpRequest * object. * * @param req An empty (newly-recycled) request object. * * @return 200 in case of a successful read of a forwarded request, 500 * if there were errors in the reading of the request, and -2 if the * server is asking the container to shut itself down. */ public int receiveNextRequest(BaseRequest req) throws IOException { if (debug > 0) { logger.log("receiveNextRequest()"); } // XXX The return values are awful. int err = 0; // if we receive an IOException here, it must be because // the remote just closed the ajp13 connection, and it's not // an error, we just need to close the AJP13 connection try { err = receive(hBuf); } catch (IOException ioe) { if(debug >0 ) logger.log( "IOException receiving message "); return -1; // Indicate it's a disconnection from the remote end } if(err < 0) { if(debug >0 ) logger.log( "Error receiving message "); return 500; } int type = (int)hBuf.getByte(); // System.out.println("XXX " + this ); return handleMessage( type, hBuf, req ); } /** Override for ajp14, temporary */ public int handleMessage( int type, Ajp13Packet hBuf, BaseRequest req ) throws IOException { if( type > handlers.length ) { logger.log( "Invalid handler " + type ); return 500; } if( debug > 0 ) logger.log( "Received " + type + " " + handlerName[type]); // Ajp14, unlogged if( ! backwardCompat && ! isLogged() ) { if( type != NegociationHandler.JK_AJP14_LOGINIT_CMD && type != NegociationHandler.JK_AJP14_LOGCOMP_CMD ) { logger.log( "Ajp14 error: not logged " + type + " " + handlerName[type]); return 300; } // else continue } // Ajp13 messages switch(type) { case RequestHandler.JK_AJP13_FORWARD_REQUEST: return reqHandler.decodeRequest(this, hBuf, req); case JK_AJP13_SHUTDOWN: return -2; } // logged || loging message AjpHandler handler=handlers[type]; if( handler==null ) { logger.log( "Unknown message " + type + handlerName[type] ); return 200; } if( debug > 0 ) logger.log( "Ajp14 handler " + handler ); return handler.handleAjpMessage( type, this, hBuf, req ); } // ==================== Servlet Input Support ================= /** @deprecated -- Will use reqHandler, make sure nobody else calls this */ public int available() throws IOException { return reqHandler.available(this); } public int doRead() throws IOException { return reqHandler.doRead( this ); } public int doRead(byte[] b, int off, int len) throws IOException { return reqHandler.doRead( this, b, off, len ); } private boolean refillReadBuffer() throws IOException { return reqHandler.refillReadBuffer(this); } public void beginSendHeaders(int status, String statusMessage, int numHeaders) throws IOException { reqHandler.beginSendHeaders( this, outBuf, status, statusMessage, numHeaders); } public void sendHeader(String name, String value) throws IOException { reqHandler.sendHeader( outBuf, name, value ); } public void endSendHeaders() throws IOException { reqHandler.endSendHeaders(this, outBuf); } public void sendHeaders(int status, MimeHeaders headers) throws IOException { reqHandler.sendHeaders(this, outBuf, status, HttpMessages.getMessage(status), headers); } public void sendHeaders(int status, String statusMessage, MimeHeaders headers) throws IOException { reqHandler.sendHeaders( this, outBuf, status, statusMessage, headers ); } public void finish() throws IOException { reqHandler.finish(this, outBuf ); } public void doWrite(byte b[], int off, int len) throws IOException { reqHandler.doWrite( this, outBuf, b, off, len ); } // ========= Internal Packet-Handling Methods ================= /** * Read N bytes from the InputStream, and ensure we got them all * Under heavy load we could experience many fragmented packets * just read Unix Network Programming to recall that a call to * read didn't ensure you got all the data you want * * from read() Linux manual * * On success, the number of bytes read is returned (zero indicates end of file), * and the file position is advanced by this number. * It is not an error if this number is smaller than the number of bytes requested; * this may happen for example because fewer bytes * are actually available right now (maybe because we were close to end-of-file, * or because we are reading from a pipe, or from a * terminal), or because read() was interrupted by a signal. * On error, -1 is returned, and errno is set appropriately. In this * case it is left unspecified whether the file position (if any) changes. * **/ private int readN(InputStream in, byte[] b, int offset, int len) throws IOException { int pos = 0; int got; while(pos < len) { got = in.read(b, pos + offset, len - pos); if (debug > 10) { logger.log("read got # " + got); } // connection just closed by remote. if (got <= 0) { // This happens periodically, as apache restarts // periodically. // It should be more gracefull ! - another feature for Ajp14 return JK_AJP13_COMM_BROKEN; } pos += got; } return pos; } /** * Read in a packet from the web server and store it in the passed-in * <CODE>Ajp13Packet</CODE> object. * * @param msg The object into which to store the incoming packet -- any * current contents will be overwritten. * * @return The number of bytes read on a successful read or -1 if there * was an error. **/ public int receive(Ajp13Packet msg) throws IOException { if (debug > 0) { logger.log("receive()"); } // XXX If the length in the packet header doesn't agree with the // actual number of bytes read, it should probably return an error // value. Also, callers of this method never use the length // returned -- should probably return true/false instead. byte b[] = msg.getBuff(); int rd = readN(in, b, 0, H_SIZE ); // XXX - connection closed (JK_AJP13_COMM_CLOSED) // - connection broken (JK_AJP13_COMM_BROKEN) // if(rd < 0) { // Most likely normal apache restart. return rd; } int len = msg.checkIn(); if( debug > 5 ) logger.log( "Received " + rd + " " + len + " " + b[0] ); // XXX check if enough space - it's assert()-ed !!! int total_read = 0; total_read = readN(in, b, H_SIZE, len); if (total_read <= 0) { logger.log("can't read body, waited #" + len); return JK_AJP13_BAD_BODY; } if (total_read != len) { logger.log( "incomplete read, waited #" + len + " got only " + total_read); return JK_AJP13_INCOMPLETE_BODY; } if (debug > 0) logger.log("receive: total read = " + total_read); return total_read; } /** * Send a packet to the web server. Works for any type of message. * * @param msg A packet with accumulated data to send to the server -- * this method will write out the length in the header. */ public void send( Ajp13Packet msg ) throws IOException { if (debug > 0) { logger.log("send()"); } msg.end(); // Write the packet header byte b[] = msg.getBuff(); int len = msg.getLen(); if (debug > 5 ) logger.log("send() " + len + " " + b[0] ); out.write( b, 0, len ); } /** * Close the socket connection to the web server. In general, sockets * are maintained across many requests, so this will not be called * after finish(). * * @see Ajp13Interceptor#processConnection */ public void close() throws IOException { if (debug > 0) { logger.log("close()"); } if(null != out) { out.close(); } if(null !=in) { in.close(); } setLogged( false ); // no more logged now } // -------------------- Debug -------------------- protected int debug = 0; public void setDebug(int debug) { this.debug = debug; } public void setLogger(Logger l) { this.logger = l; } /** * XXX place holder... */ Logger logger = new Logger(); public static class Logger { void log(String msg) { System.out.println("[Ajp13] " + msg); } void log(String msg, Throwable t) { System.out.println("[Ajp13] " + msg); t.printStackTrace(System.out); } } } 1.1 jakarta-tomcat-connectors/jk/java/org/apache/jk/Ajp13Packet.java Index: Ajp13Packet.java =================================================================== /* * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package org.apache.jk; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.Enumeration; import java.security.*; import org.apache.tomcat.util.http.MimeHeaders; import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.http.HttpMessages; import org.apache.tomcat.util.buf.HexUtils; /** * A single packet for communication between the web server and the * container. Designed to be reused many times with no creation of * garbage. Understands the format of data types for these packets. * Can be used (somewhat confusingly) for both incoming and outgoing * packets. * * @see Ajp14/Ajp13Packet * * @author Henri Gomez [[EMAIL PROTECTED]] * @author Dan Milstein [[EMAIL PROTECTED]] * @author Keith Wannamaker [[EMAIL PROTECTED]] * @author Kevin Seguin * @author Costin Manolache */ public class Ajp13Packet { public static final String DEFAULT_CHAR_ENCODING = "8859_1"; public static final int AJP13_WS_HEADER = 0x1234; public static final int AJP13_SW_HEADER = 0x4142; // 'AB' /** * encoding to use when converting byte[] <-> string */ String encoding = DEFAULT_CHAR_ENCODING; /** * Holds the bytes of the packet */ byte buff[]; /** * The current read or write position in the buffer */ int pos; /** * This actually means different things depending on whether the * packet is read or write. For read, it's the length of the * payload (excluding the header). For write, it's the length of * the packet as a whole (counting the header). Oh, well. */ int len; /** * Create a new packet with an internal buffer of given size. * @param size packet size */ public Ajp13Packet( int size ) { buff = new byte[size]; } /** * Create a new packet with given bytes * @param b this packet's bytes. */ public Ajp13Packet( byte b[] ) { buff = b; } /** * Set the encoding to use for byte[] <-> string * conversions. * @param encoding the encoding to use. */ public void setEncoding(String encoding) { this.encoding = encoding; } /** * Get the encoding used for byte[] <-> string * conversions. * @return the encoding used. */ public String getEncoding() { return encoding; } /** * Get the internal buffer * @return internal buffer */ public byte[] getBuff() { return buff; } /** * Get length. * @return length -- This actually means different things depending on whether the * packet is read or write. For read, it's the length of the * payload (excluding the header). For write, it's the length of * the packet as a whole (counting the header). Oh, well. */ public int getLen() { return len; } /** * Get offset into internal buffer. * @return offset */ public int getByteOff() { return pos; } /** * Set offset into internal buffer. * @param c new offset */ public void setByteOff(int c) { pos=c; } /** * Parse the packet header for a packet sent from the web server to * the container. Set the read position to immediately after * the header. * * @return The length of the packet payload, as encoded in the * header, or -1 if the packet doesn't have a valid header. */ public int checkIn() { pos = 0; int mark = getInt(); len = getInt(); if( mark != AJP13_WS_HEADER ) { // XXX Logging System.out.println("BAD packet " + mark); dump( "In: " ); return -1; } return len; } /** * Prepare this packet for accumulating a message from the container to * the web server. Set the write position to just after the header * (but leave the length unwritten, because it is as yet unknown). */ public void reset() { len = 4; pos = 4; buff[0] = (byte)(AJP13_SW_HEADER >> 8); buff[1] = (byte)(AJP13_SW_HEADER & 0xFF); } /** * For a packet to be sent to the web server, finish the process of * accumulating data and write the length of the data payload into * the header. */ public void end() { len = pos; setInt( 2, len-4 ); } // ============ Data Writing Methods =================== /** * Write an 32 bit integer at an arbitrary position in the packet,but don't * change the write position. * * @param bpos The 0-indexed position within the buffer at which to * write the integer (where 0 is the beginning of the header). * @param val The integer to write. */ private void setInt( int bPos, int val ) { buff[bPos] = (byte) ((val >>> 8) & 0xFF); buff[bPos+1] = (byte) (val & 0xFF); } public void appendInt( int val ) { setInt( pos, val ); pos += 2; } public void appendByte( byte val ) { buff[pos++] = val; } public void appendBool( boolean val) { buff[pos++] = (byte) (val ? 1 : 0); } /** * Write a String out at the current write position. Strings are * encoded with the length in two bytes first, then the string, and * then a terminating \0 (which is <B>not</B> included in the * encoded length). The terminator is for the convenience of the C * code, where it saves a round of copying. A null string is * encoded as a string with length 0. */ public void appendString(String str) throws UnsupportedEncodingException { // Dual use of the buffer - as Ajp13Packet and as OutputBuffer // The idea is simple - fewer buffers, smaller footprint and less // memcpy. The code is a bit tricky, but only local to this // function. if(str == null) { setInt( pos, 0); buff[pos + 2] = 0; pos += 3; return; } // // XXX i don't have OutputBuffer in tc4... ks. // fix this later... // byte[] bytes = str.getBytes(encoding); appendBytes(bytes, 0, bytes.length); /* XXX XXX XXX XXX Try to add it back. int strStart=pos; // This replaces the old ( buggy and slow ) str.length() // and str.getBytes(). str.length() is chars, may be != bytes // and getBytes is _very_ slow. // XXX setEncoding !!! ob.setByteOff( pos+2 ); try { ob.write( str ); ob.flushChars(); } catch( IOException ex ) { ex.printStackTrace(); } int strEnd=ob.getByteOff(); buff[strEnd]=0; // The \0 terminator int strLen=strEnd-strStart; setInt( pos, strEnd - strStart ); pos += strLen + 3; */ } /** * Copy a chunk of bytes into the packet, starting at the current * write position. The chunk of bytes is encoded with the length * in two bytes first, then the data itself, and finally a * terminating \0 (which is <B>not</B> included in the encoded * length). * * @param b The array from which to copy bytes. * @param off The offset into the array at which to start copying * @param len The number of bytes to copy. */ public void appendBytes( byte b[], int off, int numBytes ) { appendInt( numBytes ); if( pos + numBytes >= buff.length ) { System.out.println("Buffer overflow " + buff.length + " " + pos + " " + numBytes ); // XXX Log } System.arraycopy( b, off, buff, pos, numBytes); buff[pos + numBytes] = 0; // Terminating \0 pos += numBytes + 1; } /** * Write a 32 bits integer at an arbitrary position in the packet, but don't * change the write position. * * @param bpos The 0-indexed position within the buffer at which to * write the integer (where 0 is the beginning of the header). * @param val The integer to write. */ private void setLongInt( int bPos, int val ) { buff[bPos] = (byte) ((val >>> 24) & 0xFF); buff[bPos+1] = (byte) ((val >>> 16) & 0xFF); buff[bPos+2] = (byte) ((val >>> 8) & 0xFF); buff[bPos+3] = (byte) (val & 0xFF); } public void appendLongInt( int val ) { setLongInt( pos, val ); pos += 4; } /** * Copy a chunk of bytes into the packet, starting at the current * write position. The chunk of bytes IS NOT ENCODED with ANY length * header. * * @param b The array from which to copy bytes. * @param off The offset into the array at which to start copying * @param len The number of bytes to copy. */ public void appendXBytes(byte[] b, int off, int numBytes) { if( pos + numBytes > buff.length ) { System.out.println("appendXBytes - Buffer overflow " + buff.length + " " + pos + " " + numBytes ); // XXX Log } System.arraycopy( b, off, buff, pos, numBytes); pos += numBytes; } // ============ Data Reading Methods =================== /** * Read an integer from packet, and advance the read position past * it. Integers are encoded as two unsigned bytes with the * high-order byte first, and, as far as I can tell, in * little-endian order within each byte. */ public int getInt() { int result = peekInt(); pos += 2; return result; } /** * Read an integer from the packet, but don't advance the read * position past it. */ public int peekInt() { int b1 = buff[pos] & 0xFF; // No swap, Java order int b2 = buff[pos + 1] & 0xFF; return (b1<<8) + b2; } public byte getByte() { byte res = buff[pos]; pos++; return res; } public byte peekByte() { return buff[pos]; } public boolean getBool() { return (getByte() == (byte) 1); } public void getMessageBytes(MessageBytes mb) { int length = getInt(); if( (length == 0xFFFF) || (length == -1) ) { mb.setString( null ); return; } mb.setBytes( buff, pos, length ); pos += length; pos++; // Skip the terminating \0 } public MessageBytes addHeader(MimeHeaders headers) { int length = getInt(); if( (length == 0xFFFF) || (length == -1) ) { return null; } MessageBytes vMB=headers.addValue( buff, pos, length ); pos += length; pos++; // Skip the terminating \0 return vMB; } /** * Read a String from the packet, and advance the read position * past it. See appendString for details on string encoding. **/ public String getString() throws java.io.UnsupportedEncodingException { int length = getInt(); if( (length == 0xFFFF) || (length == -1) ) { // System.out.println("null string " + length); return null; } String s = new String(buff, pos, length, encoding); pos += length; pos++; // Skip the terminating \0 return s; } /** * Copy a chunk of bytes from the packet into an array and advance * the read position past the chunk. See appendBytes() for details * on the encoding. * * @return The number of bytes copied. */ public int getBytes(byte dest[]) { int length = getInt(); if( length > buff.length ) { // XXX Should be if(pos + length > buff.legth)? System.out.println("XXX Assert failed, buff too small "); } if( (length == 0xFFFF) || (length == -1) ) { System.out.println("null string " + length); return 0; } System.arraycopy( buff, pos, dest, 0, length ); pos += length; pos++; // Skip terminating \0 XXX I believe this is wrong but harmless return length; } /** * Read a 32 bits integer from packet, and advance the read position past * it. Integers are encoded as four unsigned bytes with the * high-order byte first, and, as far as I can tell, in * little-endian order within each byte. */ public int getLongInt() { int result = peekLongInt(); pos += 4; return result; } /** * Copy a chunk of bytes from the packet into an array and advance * the read position past the chunk. See appendXBytes() for details * on the encoding. * * @return The number of bytes copied. */ public int getXBytes(byte[] dest, int length) { if( length > buff.length ) { // XXX Should be if(pos + length > buff.legth)? System.out.println("XXX Assert failed, buff too small "); } System.arraycopy( buff, pos, dest, 0, length ); pos += length; return length; } /** * Read a 32 bits integer from the packet, but don't advance the read * position past it. */ public int peekLongInt() { int b1 = buff[pos] & 0xFF; // No swap, Java order b1 <<= 8; b1 |= (buff[pos + 1] & 0xFF); b1 <<= 8; b1 |= (buff[pos + 2] & 0xFF); b1 <<=8; b1 |= (buff[pos + 3] & 0xFF); return b1; } // ============== Debugging code ========================= private String hex( int x ) { // if( x < 0) x=256 + x; String h=Integer.toHexString( x ); if( h.length() == 1 ) h = "0" + h; return h.substring( h.length() - 2 ); } private void hexLine( int start ) { for( int i=start; i< start+16 ; i++ ) { if( i < len + 4) System.out.print( hex( buff[i] ) + " "); else System.out.print( " " ); } System.out.print(" | "); for( int i=start; i < start+16 && i < len + 4; i++ ) { if( Character.isLetterOrDigit( (char)buff[i] )) System.out.print( new Character((char)buff[i]) ); else System.out.print( "." ); } System.out.println(); } public void dump(String msg) { System.out.println( msg + ": " + buff + " " + pos +"/" + (len + 4)); for( int j=0; j < len + 4; j+=16 ) hexLine( j ); System.out.println(); } } 1.1 jakarta-tomcat-connectors/jk/java/org/apache/jk/AjpHandler.java Index: AjpHandler.java =================================================================== /* * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package org.apache.jk; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.Enumeration; import java.security.*; import org.apache.tomcat.util.http.*; import org.apache.tomcat.util.buf.*; /** * Base class for handlers of Ajp messages. Jk provide a simple bidirectional * communication mechanism between the web server and a servlet container. It is * based on messages that are passed between the 2 server, using a single * thread on each side. * * The container side must be able to deal with at least the "REQUEST FORWARD", * the server side must be able to deal with at least "HEADERS", "BODY", * "END" messages. * * @author Henri Gomez * @author Costin Manolache */ public class AjpHandler { public static final int UNKNOWN=-1; Ajp13 channel; public void init( Ajp13 channel ) { this.channel=channel; } /** Execute the callback */ public int handleAjpMessage( int type, Ajp13 channel, Ajp13Packet ajp, BaseRequest req ) throws IOException { return UNKNOWN; } } 1.1 jakarta-tomcat-connectors/jk/java/org/apache/jk/NegociationHandler.java Index: NegociationHandler.java =================================================================== /* * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package org.apache.jk; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.Enumeration; import java.security.*; import org.apache.tomcat.util.http.*; import org.apache.tomcat.util.buf.*; /** * Handler for the protocol negotiation. It will authenticate and * exchange information about supported messages on each end. * * * @author Henri Gomez [[EMAIL PROTECTED]] * @author Dan Milstein [[EMAIL PROTECTED]] * @author Keith Wannamaker [[EMAIL PROTECTED]] * @author Costin Manolache */ public class NegociationHandler extends AjpHandler { public static final byte JK_AJP13_SHUTDOWN=7; // 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( 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 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 ); } } 1.1 jakarta-tomcat-connectors/jk/java/org/apache/jk/RequestHandler.java Index: RequestHandler.java =================================================================== /* * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package org.apache.jk; import java.io.*; import java.net.Socket; import java.util.Enumeration; import java.security.*; import java.security.cert.*; import org.apache.tomcat.util.http.*; import org.apache.tomcat.util.buf.*; /** * Handle messages related with basic request information. * * This object can handle the following incoming messages: * - "FORWARD_REQUEST" input message ( sent when a request is passed from the web server ) * - "RECEIVE_BODY_CHUNK" input ( sent by container to pass more body, in response to GET_BODY_CHUNK ) * * It can handle the following outgoing messages: * - SEND_HEADERS. Pass the status code and headers. * - SEND_BODY_CHUNK. Send a chunk of body * - GET_BODY_CHUNK. Request a chunk of body data * - END_RESPONSE. Notify the end of a request processing. * * @author Henri Gomez [[EMAIL PROTECTED]] * @author Dan Milstein [[EMAIL PROTECTED]] * @author Keith Wannamaker [[EMAIL PROTECTED]] * @author Costin Manolache */ public class RequestHandler extends AjpHandler { // XXX Will move to a registry system. // Prefix codes for message types from server to container public static final byte JK_AJP13_FORWARD_REQUEST = 2; // Prefix codes for message types from container to server public static final byte JK_AJP13_SEND_BODY_CHUNK = 3; public static final byte JK_AJP13_SEND_HEADERS = 4; public static final byte JK_AJP13_END_RESPONSE = 5; public static final byte JK_AJP13_GET_BODY_CHUNK = 6; // Integer codes for common response header strings public static final int SC_RESP_CONTENT_TYPE = 0xA001; public static final int SC_RESP_CONTENT_LANGUAGE = 0xA002; public static final int SC_RESP_CONTENT_LENGTH = 0xA003; public static final int SC_RESP_DATE = 0xA004; public static final int SC_RESP_LAST_MODIFIED = 0xA005; public static final int SC_RESP_LOCATION = 0xA006; public static final int SC_RESP_SET_COOKIE = 0xA007; public static final int SC_RESP_SET_COOKIE2 = 0xA008; public static final int SC_RESP_SERVLET_ENGINE = 0xA009; public static final int SC_RESP_STATUS = 0xA00A; public static final int SC_RESP_WWW_AUTHENTICATE = 0xA00B; // Integer codes for common (optional) request attribute names public static final byte SC_A_CONTEXT = 1; // XXX Unused public static final byte SC_A_SERVLET_PATH = 2; // XXX Unused public static final byte SC_A_REMOTE_USER = 3; public static final byte SC_A_AUTH_TYPE = 4; public static final byte SC_A_QUERY_STRING = 5; public static final byte SC_A_JVM_ROUTE = 6; public static final byte SC_A_SSL_CERT = 7; public static final byte SC_A_SSL_CIPHER = 8; public static final byte SC_A_SSL_SESSION = 9; public static final byte SC_A_SSL_KEYSIZE = 11; // Used for attributes which are not in the list above public static final byte SC_A_REQ_ATTRIBUTE = 10; // Terminates list of attributes public static final byte SC_A_ARE_DONE = (byte)0xFF; // Translates integer codes to names of HTTP methods public static final String []methodTransArray = { "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", "ACL", "REPORT", "VERSION-CONTROL", "CHECKIN", "CHECKOUT", "UNCHECKOUT", "SEARCH" }; // id's for common request headers public static final int SC_REQ_ACCEPT = 1; public static final int SC_REQ_ACCEPT_CHARSET = 2; public static final int SC_REQ_ACCEPT_ENCODING = 3; public static final int SC_REQ_ACCEPT_LANGUAGE = 4; public static final int SC_REQ_AUTHORIZATION = 5; public static final int SC_REQ_CONNECTION = 6; public static final int SC_REQ_CONTENT_TYPE = 7; public static final int SC_REQ_CONTENT_LENGTH = 8; public static final int SC_REQ_COOKIE = 9; public static final int SC_REQ_COOKIE2 = 10; public static final int SC_REQ_HOST = 11; public static final int SC_REQ_PRAGMA = 12; public static final int SC_REQ_REFERER = 13; public static final int SC_REQ_USER_AGENT = 14; // AJP14 new header public static final byte SC_A_SSL_KEY_SIZE = 11; // XXX ??? // Translates integer codes to request header names public static final String []headerTransArray = { "accept", "accept-charset", "accept-encoding", "accept-language", "authorization", "connection", "content-type", "content-length", "cookie", "cookie2", "host", "pragma", "referer", "user-agent" }; public RequestHandler() { } public void init( Ajp13 ajp14 ) { // register incoming message handlers ajp14.registerMessageType( JK_AJP13_FORWARD_REQUEST, "JK_AJP13_FORWARD_REQUEST", this, null); // 2 // register outgoing messages handler ajp14.registerMessageType( JK_AJP13_SEND_BODY_CHUNK, // 3 "JK_AJP13_SEND_BODY_CHUNK", this,null ); ajp14.registerMessageType( JK_AJP13_SEND_HEADERS, // 4 "JK_AJP13_SEND_HEADERS", this,null ); ajp14.registerMessageType( JK_AJP13_END_RESPONSE, // 5 "JK_AJP13_END_RESPONSE", this,null ); ajp14.registerMessageType( JK_AJP13_GET_BODY_CHUNK, // 6 "JK_AJP13_GET_BODY_CHUNK", this, null ); } // -------------------- Incoming message -------------------- public int handleAjpMessage( int type, Ajp13 channel, Ajp13Packet ajp, BaseRequest req ) throws IOException { switch( type ) { case RequestHandler.JK_AJP13_FORWARD_REQUEST: return decodeRequest(channel, channel.hBuf, req ); default: return UNKNOWN; } } /** * Parse a FORWARD_REQUEST packet from the web server and store its * properties in the passed-in request object. * * @param req An empty (newly-recycled) request object. * @param msg Holds the packet which has just been sent by the web * server, with its read position just past the packet header (which in * this case includes the prefix code for FORWARD_REQUEST). * * @return 200 in case of a successful decoduing, 500 in case of error. */ protected int decodeRequest(Ajp13 ch, Ajp13Packet msg, BaseRequest req) throws IOException { if (debug > 0) { log("decodeRequest()"); } // XXX Awful return values boolean isSSL = false; // Translate the HTTP method code to a String. byte methodCode = msg.getByte(); req.method().setString(methodTransArray[(int)methodCode - 1]); msg.getMessageBytes(req.protocol()); msg.getMessageBytes(req.requestURI()); msg.getMessageBytes(req.remoteAddr()); msg.getMessageBytes(req.remoteHost()); msg.getMessageBytes(req.serverName()); req.setServerPort(msg.getInt()); isSSL = msg.getBool(); // Decode headers MimeHeaders headers = req.headers(); int hCount = msg.getInt(); for(int i = 0 ; i < hCount ; i++) { String hName = null; // Header names are encoded as either an integer code starting // with 0xA0, or as a normal string (in which case the first // two bytes are the length). int isc = msg.peekInt(); int hId = isc & 0xFF; MessageBytes vMB=null; isc &= 0xFF00; if(0xA000 == isc) { msg.getInt(); // To advance the read position hName = headerTransArray[hId - 1]; vMB= headers.addValue(hName); } else { // XXX Not very elegant vMB = msg.addHeader(headers); if (vMB == null) { return 500; // wrong packet } } msg.getMessageBytes(vMB); // set content length, if this is it... if (hId == SC_REQ_CONTENT_LENGTH) { int contentLength = (vMB == null) ? -1 : vMB.getInt(); req.setContentLength(contentLength); } else if (hId == SC_REQ_CONTENT_TYPE) { ByteChunk bchunk = vMB.getByteChunk(); req.contentType().setBytes(bchunk.getBytes(), bchunk.getOffset(), bchunk.getLength()); } } byte attributeCode; for(attributeCode = msg.getByte() ; attributeCode != SC_A_ARE_DONE ; attributeCode = msg.getByte()) { switch(attributeCode) { case SC_A_CONTEXT : break; case SC_A_SERVLET_PATH : break; case SC_A_REMOTE_USER : msg.getMessageBytes(req.remoteUser()); break; case SC_A_AUTH_TYPE : msg.getMessageBytes(req.authType()); break; case SC_A_QUERY_STRING : msg.getMessageBytes(req.queryString()); break; case SC_A_JVM_ROUTE : msg.getMessageBytes(req.jvmRoute()); break; case SC_A_SSL_CERT : isSSL = true; // Transform the string into certificate. String certString = msg.getString(); byte[] certData = certString.getBytes(); ByteArrayInputStream bais = new ByteArrayInputStream(certData); // Fill the first element. X509Certificate jsseCerts[] = null; try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(bais); jsseCerts = new X509Certificate[1]; jsseCerts[0] = cert; } catch(java.security.cert.CertificateException e) { log("Certificate convertion failed" + e ); } req.setAttribute("javax.servlet.request.X509Certificate", jsseCerts); break; case SC_A_SSL_CIPHER : isSSL = true; req.setAttribute("javax.servlet.request.cipher_suite", msg.getString()); break; case SC_A_SSL_SESSION : isSSL = true; req.setAttribute("javax.servlet.request.ssl_session", msg.getString()); break; case SC_A_REQ_ATTRIBUTE : req.setAttribute(msg.getString(), msg.getString()); break; case SC_A_SSL_KEY_SIZE: // Ajp13 ! req.setAttribute("javax.servlet.request.key_size", Integer.toString(msg.getInt())); return 200; default: return 500; // Error } } if(isSSL) { req.setScheme(req.SCHEME_HTTPS); req.setSecure(true); } // set cookies on request now that we have all headers req.cookies().setHeaders(req.headers()); // Check to see if there should be a body packet coming along // immediately after if(req.getContentLength() > 0) { /* Read present data */ int err = ch.receive(ch.inBuf); if(err < 0) { return 500; } ch.blen = ch.inBuf.peekInt(); ch.pos = 0; ch.inBuf.getBytes(ch.bodyBuff); } if (debug > 5) { log(req.toString()); } return 200; // Success } // -------------------- Messages from container to server ------------------ /** * Send the HTTP headers back to the web server and on to the browser. * * @param status The HTTP status code to send. * @param statusMessage the HTTP status message to send. * @param headers The set of all headers. */ public void sendHeaders(Ajp13 ch, Ajp13Packet outBuf, int status, String statusMessage, MimeHeaders headers) throws IOException { // XXX if more headers that MAX_SIZE, send 2 packets! if( statusMessage==null ) statusMessage=HttpMessages.getMessage(status); outBuf.reset(); outBuf.appendByte(JK_AJP13_SEND_HEADERS); outBuf.appendInt(status); outBuf.appendString(statusMessage); int numHeaders = headers.size(); outBuf.appendInt(numHeaders); for( int i=0 ; i < numHeaders ; i++ ) { String headerName = headers.getName(i).toString(); int sc = headerNameToSc(headerName); if(-1 != sc) { outBuf.appendInt(sc); } else { outBuf.appendString(headerName); } outBuf.appendString(headers.getValue(i).toString() ); } outBuf.end(); ch.send(outBuf); } /** * Signal the web server that the servlet has finished handling this * request, and that the connection can be reused. */ public void finish(Ajp13 ch, Ajp13Packet outBuf) throws IOException { if (debug > 0) log("finish()"); outBuf.reset(); outBuf.appendByte(JK_AJP13_END_RESPONSE); outBuf.appendBool(true); // Reuse this connection outBuf.end(); ch.send(outBuf); } /** * Send a chunk of response body data to the web server and on to the * browser. * * @param b A huffer of bytes to send. * @param off The offset into the buffer from which to start sending. * @param len The number of bytes to send. */ public void doWrite(Ajp13 ch, Ajp13Packet outBuf, byte b[], int off, int len) throws IOException { if (debug > 0) log("doWrite(byte[], " + off + ", " + len + ")"); int sent = 0; while(sent < len) { int to_send = len - sent; to_send = to_send > Ajp13.MAX_SEND_SIZE ? Ajp13.MAX_SEND_SIZE : to_send; outBuf.reset(); outBuf.appendByte(JK_AJP13_SEND_BODY_CHUNK); outBuf.appendBytes(b, off + sent, to_send); ch.send(outBuf); sent += to_send; } } // -------------------- Utils -------------------- /** * Translate an HTTP response header name to an integer code if * possible. Case is ignored. * * @param name The name of the response header to translate. * * @return The code for that header name, or -1 if no code exists. */ protected int headerNameToSc(String name) { switch(name.charAt(0)) { case 'c': case 'C': if(name.equalsIgnoreCase("Content-Type")) { return SC_RESP_CONTENT_TYPE; } else if(name.equalsIgnoreCase("Content-Language")) { return SC_RESP_CONTENT_LANGUAGE; } else if(name.equalsIgnoreCase("Content-Length")) { return SC_RESP_CONTENT_LENGTH; } break; case 'd': case 'D': if(name.equalsIgnoreCase("Date")) { return SC_RESP_DATE; } break; case 'l': case 'L': if(name.equalsIgnoreCase("Last-Modified")) { return SC_RESP_LAST_MODIFIED; } else if(name.equalsIgnoreCase("Location")) { return SC_RESP_LOCATION; } break; case 's': case 'S': if(name.equalsIgnoreCase("Set-Cookie")) { return SC_RESP_SET_COOKIE; } else if(name.equalsIgnoreCase("Set-Cookie2")) { return SC_RESP_SET_COOKIE2; } break; case 'w': case 'W': if(name.equalsIgnoreCase("WWW-Authenticate")) { return SC_RESP_WWW_AUTHENTICATE; } break; } return -1; } private static int debug=0; void log(String s) { System.out.println("RequestHandler: " + s ); } // ==================== Servlet Input Support ================= // XXX DEPRECATED public int available(Ajp13 ch) throws IOException { if (debug > 0) { log("available()"); } if (ch.pos >= ch.blen) { if( ! refillReadBuffer(ch)) { return 0; } } return ch.blen - ch.pos; } /** * Return the next byte of request body data (to a servlet). * * @see Request#doRead */ public int doRead(Ajp13 ch) throws IOException { if (debug > 0) { log("doRead()"); } if(ch.pos >= ch.blen) { if( ! refillReadBuffer(ch)) { return -1; } } return (char) ch.bodyBuff[ch.pos++]; } /** * Store a chunk of request data into the passed-in byte buffer. * * @param b A buffer to fill with data from the request. * @param off The offset in the buffer at which to start filling. * @param len The number of bytes to copy into the buffer. * * @return The number of bytes actually copied into the buffer, or -1 * if the end of the stream has been reached. * * @see Request#doRead */ public int doRead(Ajp13 ch, byte[] b, int off, int len) throws IOException { if (debug > 0) { log("doRead(byte[], int, int)"); } if(ch.pos >= ch.blen) { if( ! refillReadBuffer(ch)) { return -1; } } if(ch.pos + len <= ch.blen) { // Fear the off by one error // Sanity check b.length > off + len? System.arraycopy(ch.bodyBuff, ch.pos, b, off, len); ch.pos += len; return len; } // Not enough data (blen < pos + len) int toCopy = len; while(toCopy > 0) { int bytesRemaining = ch.blen - ch.pos; if(bytesRemaining < 0) bytesRemaining = 0; int c = bytesRemaining < toCopy ? bytesRemaining : toCopy; System.arraycopy(ch.bodyBuff, ch.pos, b, off, c); toCopy -= c; off += c; ch.pos += c; // In case we exactly consume the buffer if(toCopy > 0) if( ! refillReadBuffer(ch)) { // Resets blen and pos break; } } return len - toCopy; } /** * Get more request body data from the web server and store it in the * internal buffer. * * @return true if there is more data, false if not. */ public boolean refillReadBuffer(Ajp13 ch) throws IOException { if (debug > 0) { log("refillReadBuffer()"); } // If the server returns an empty packet, assume that that end of // the stream has been reached (yuck -- fix protocol??). // Why not use outBuf?? ch.inBuf.reset(); ch.inBuf.appendByte(JK_AJP13_GET_BODY_CHUNK); ch.inBuf.appendInt(Ajp13.MAX_READ_SIZE); ch.send(ch.inBuf); int err = ch.receive(ch.inBuf); if(err < 0) { throw new IOException(); } ch.blen = ch.inBuf.peekInt(); ch.pos = 0; ch.inBuf.getBytes(ch.bodyBuff); return (ch.blen > 0); } // ==================== Servlet Output Support ================= /** */ public void beginSendHeaders(Ajp13 ch, Ajp13Packet outBuf, int status, String statusMessage, int numHeaders) throws IOException { if (debug > 0) { log("sendHeaders()"); } // XXX if more headers that MAX_SIZE, send 2 packets! outBuf.reset(); outBuf.appendByte(JK_AJP13_SEND_HEADERS); if (debug > 0) { log("status is: " + status + "(" + statusMessage + ")"); } // set status code and message outBuf.appendInt(status); outBuf.appendString(statusMessage); // write the number of headers... outBuf.appendInt(numHeaders); } public void sendHeader(Ajp13Packet outBuf, String name, String value) throws IOException { int sc = headerNameToSc(name); if(-1 != sc) { outBuf.appendInt(sc); } else { outBuf.appendString(name); } outBuf.appendString(value); } public void endSendHeaders(Ajp13 ch, Ajp13Packet outBuf) throws IOException { outBuf.end(); ch.send(outBuf); } /** * Send the HTTP headers back to the web server and on to the browser. * * @param status The HTTP status code to send. * @param headers The set of all headers. */ public void sendHeaders(Ajp13 ch, Ajp13Packet outBuf, int status, MimeHeaders headers) throws IOException { sendHeaders(ch, outBuf, status, HttpMessages.getMessage(status), headers); } }
-- To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]> For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>