001: /*
002: * Copyright 1999-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.ajp;
018:
019: import java.io.IOException;
020: import java.security.MessageDigest;
021:
022: import org.apache.tomcat.util.buf.HexUtils;
023: import org.apache.tomcat.util.http.BaseRequest;
024:
025: /**
026: * Handler for the protocol negotiation. It will authenticate and
027: * exchange information about supported messages on each end.
028: *
029: *
030: * @author Henri Gomez [hgomez@apache.org]
031: * @author Dan Milstein [danmil@shore.net]
032: * @author Keith Wannamaker [Keith@Wannamaker.org]
033: * @author Costin Manolache
034: */
035: public class NegociationHandler extends AjpHandler {
036: // Initial Login Phase (web server -> servlet engine)
037: public static final byte JK_AJP14_LOGINIT_CMD = 0x10;
038:
039: // Second Login Phase (servlet engine -> web server), md5 seed is received
040: public static final byte JK_AJP14_LOGSEED_CMD = 0x11;
041:
042: // Third Login Phase (web server -> servlet engine),
043: // md5 of seed + secret is sent
044: public static final byte JK_AJP14_LOGCOMP_CMD = 0x12;
045:
046: // Login Accepted (servlet engine -> web server)
047: public static final byte JK_AJP14_LOGOK_CMD = 0x13;
048:
049: // Login Rejected (servlet engine -> web server), will be logged
050: public static final byte JK_AJP14_LOGNOK_CMD = 0x14;
051:
052: // Context Query (web server -> servlet engine),
053: // which URI are handled by servlet engine ?
054: public static final byte JK_AJP14_CONTEXT_QRY_CMD = 0x15;
055:
056: // Context Info (servlet engine -> web server), URI handled response
057: public static final byte JK_AJP14_CONTEXT_INFO_CMD = 0x16;
058:
059: // Context Update (servlet engine -> web server), status of context changed
060: public static final byte JK_AJP14_CONTEXT_UPDATE_CMD = 0x17;
061:
062: // Servlet Engine Status (web server -> servlet engine),
063: // what's the status of the servlet engine ?
064: public static final byte JK_AJP14_STATUS_CMD = 0x18;
065:
066: // Secure Shutdown command (web server -> servlet engine),
067: //please servlet stop yourself.
068: public static final byte JK_AJP14_SHUTDOWN_CMD = 0x19;
069:
070: // Secure Shutdown command Accepted (servlet engine -> web server)
071: public static final byte JK_AJP14_SHUTOK_CMD = 0x1A;
072:
073: // Secure Shutdown Rejected (servlet engine -> web server)
074: public static final byte JK_AJP14_SHUTNOK_CMD = 0x1B;
075:
076: // Context Status (web server -> servlet engine),
077: //what's the status of the context ?
078: public static final byte JK_AJP14_CONTEXT_STATE_CMD = 0x1C;
079:
080: // Context Status Reply (servlet engine -> web server), status of context
081: public static final byte JK_AJP14_CONTEXT_STATE_REP_CMD = 0x1D;
082:
083: // Unknown Packet Reply (web server <-> servlet engine),
084: //when a packet couldn't be decoded
085: public static final byte JK_AJP14_UNKNOW_PACKET_CMD = 0x1E;
086:
087: // -------------------- Other constants --------------------
088:
089: // Entropy Packet Size
090: public static final int AJP14_ENTROPY_SEED_LEN = 32;
091: public static final int AJP14_COMPUTED_KEY_LEN = 32;
092:
093: // web-server want context info after login
094: public static final int AJP14_CONTEXT_INFO_NEG = 0x80000000;
095:
096: // web-server want context updates
097: public static final int AJP14_CONTEXT_UPDATE_NEG = 0x40000000;
098:
099: // web-server want compressed stream
100: public static final int AJP14_GZIP_STREAM_NEG = 0x20000000;
101:
102: // web-server want crypted DES56 stream with secret key
103: public static final int AJP14_DES56_STREAM_NEG = 0x10000000;
104:
105: // Extended info on server SSL vars
106: public static final int AJP14_SSL_VSERVER_NEG = 0x08000000;
107:
108: // Extended info on client SSL vars
109: public static final int AJP14_SSL_VCLIENT_NEG = 0x04000000;
110:
111: // Extended info on crypto SSL vars
112: public static final int AJP14_SSL_VCRYPTO_NEG = 0x02000000;
113:
114: // Extended info on misc SSL vars
115: public static final int AJP14_SSL_VMISC_NEG = 0x01000000;
116:
117: // mask of protocol supported
118: public static final int AJP14_PROTO_SUPPORT_AJPXX_NEG = 0x00FF0000;
119:
120: // communication could use AJP14
121: public static final int AJP14_PROTO_SUPPORT_AJP14_NEG = 0x00010000;
122:
123: // communication could use AJP15
124: public static final int AJP14_PROTO_SUPPORT_AJP15_NEG = 0x00020000;
125:
126: // communication could use AJP16
127: public static final int AJP14_PROTO_SUPPORT_AJP16_NEG = 0x00040000;
128:
129: // Some failure codes
130: public static final int AJP14_BAD_KEY_ERR = 0xFFFFFFFF;
131: public static final int AJP14_ENGINE_DOWN_ERR = 0xFFFFFFFE;
132: public static final int AJP14_RETRY_LATER_ERR = 0xFFFFFFFD;
133: public static final int AJP14_SHUT_AUTHOR_FAILED_ERR = 0xFFFFFFFC;
134:
135: // Some status codes
136: public static final byte AJP14_CONTEXT_DOWN = 0x01;
137: public static final byte AJP14_CONTEXT_UP = 0x02;
138: public static final byte AJP14_CONTEXT_OK = 0x03;
139:
140: // -------------------- Parameters --------------------
141: String containerSignature = "Ajp14-based container";
142: String seed = "seed";// will use random
143: String password;
144:
145: int webserverNegociation = 0;
146:
147: // String webserverName;
148:
149: public NegociationHandler() {
150: setSeed("myveryrandomentropy");
151: setPassword("myverysecretkey");
152: }
153:
154: public void setContainerSignature(String s) {
155: containerSignature = s;
156: }
157:
158: // -------------------- State --------------------
159:
160: // public String getWebserverName() {
161: // return webserverName;
162: // }
163:
164: // -------------------- Parameters --------------------
165:
166: /**
167: * Set the original entropy seed
168: */
169: public void setSeed(String pseed) {
170: String[] credentials = new String[1];
171: credentials[0] = pseed;
172: seed = digest(credentials, "md5");
173: }
174:
175: /**
176: * Get the original entropy seed
177: */
178: public String getSeed() {
179: return seed;
180: }
181:
182: /**
183: * Set the secret password
184: */
185: public void setPassword(String ppwd) {
186: password = ppwd;
187: }
188:
189: /**
190: * Get the secret password
191: */
192: public String getPassword() {
193: return password;
194: }
195:
196: // -------------------- Initialization --------------------
197:
198: public void init(Ajp13 ajp14) {
199: super .init(ajp14);
200: // register incoming message handlers
201: ajp14.registerMessageType(JK_AJP14_LOGINIT_CMD,
202: "JK_AJP14_LOGINIT_CMD", this , null); //
203: ajp14.registerMessageType(JK_AJP14_LOGCOMP_CMD,
204: "JK_AJP14_LOGCOMP_CMD", this , null); //
205: ajp14.registerMessageType(RequestHandler.JK_AJP13_SHUTDOWN,
206: "JK_AJP13_SHUTDOWN", this , null); //
207: ajp14.registerMessageType(JK_AJP14_CONTEXT_QRY_CMD,
208: "JK_AJP14_CONTEXT_QRY_CMD", this , null); //
209: ajp14.registerMessageType(JK_AJP14_STATUS_CMD,
210: "JK_AJP14_STATUS_CMD", this , null); //
211: ajp14.registerMessageType(JK_AJP14_SHUTDOWN_CMD,
212: "JK_AJP14_SHUTDOWN_CMD", this , null); //
213: ajp14.registerMessageType(JK_AJP14_CONTEXT_STATE_CMD,
214: "JK_AJP14_CONTEXT_STATE_CMD", this , null); //
215: ajp14.registerMessageType(JK_AJP14_UNKNOW_PACKET_CMD,
216: "JK_AJP14_UNKNOW_PACKET_CMD", this , null); //
217:
218: // register outgoing messages handler
219: ajp14.registerMessageType(JK_AJP14_LOGNOK_CMD,
220: "JK_AJP14_LOGNOK_CMD", this , null);
221:
222: }
223:
224: // -------------------- Dispatch --------------------
225:
226: public int handleAjpMessage(int type, Ajp13 ch, Ajp13Packet hBuf,
227: BaseRequest req) throws IOException {
228: System.out.println("handleAjpMessage: " + type);
229: Ajp13Packet outBuf = ch.outBuf;
230: // Valid requests when not logged:
231: switch (type) {
232: case JK_AJP14_LOGINIT_CMD:
233: return handleLogInit(ch, hBuf, outBuf);
234: case JK_AJP14_LOGCOMP_CMD:
235: return handleLogComp(ch, hBuf, outBuf);
236: case RequestHandler.JK_AJP13_SHUTDOWN:
237: return -2;
238: case JK_AJP14_CONTEXT_QRY_CMD:
239: return handleContextQuery(ch, hBuf, outBuf);
240: case JK_AJP14_STATUS_CMD:
241: return handleStatus(hBuf, outBuf);
242: case JK_AJP14_SHUTDOWN_CMD:
243: return handleShutdown(hBuf, outBuf);
244: case JK_AJP14_CONTEXT_STATE_CMD:
245: return handleContextState(hBuf, outBuf);
246: case JK_AJP14_UNKNOW_PACKET_CMD:
247: return handleUnknowPacket(hBuf, outBuf);
248: default:
249: log("unknown command " + type + " received");
250: return 200; // XXX This is actually an error condition
251: }
252: //return UNKNOWN;
253: }
254:
255: //----------- Implementation for various protocol commands -----------
256:
257: /**
258: * Handle the Initial Login Message from Web-Server
259: *
260: * Get the requested Negociation Flags
261: * Get also the Web-Server Name
262: *
263: * Send Login Seed (MD5 of seed)
264: */
265: private int handleLogInit(Ajp13 ch, Ajp13Packet msg,
266: Ajp13Packet outBuf) throws IOException {
267: webserverNegociation = msg.getLongInt();
268: String webserverName = msg.getString();
269: log("in handleLogInit with nego "
270: + decodeNegociation(webserverNegociation)
271: + " from webserver " + webserverName);
272:
273: outBuf.reset();
274: outBuf.appendByte(JK_AJP14_LOGSEED_CMD);
275: String[] credentials = new String[1];
276: credentials[0] = getSeed();
277: outBuf.appendXBytes(getSeed().getBytes(), 0,
278: AJP14_ENTROPY_SEED_LEN);
279: log("in handleLogInit: sent entropy " + getSeed());
280: outBuf.end();
281: ch.send(outBuf);
282: return 304;
283: }
284:
285: /**
286: * Handle the Second Phase of Login (accreditation)
287: *
288: * Get the MD5 digest of entropy + secret password
289: * If the authentification is valid send back LogOk
290: * If the authentification failed send back LogNok
291: */
292: private int handleLogComp(Ajp13 ch, Ajp13Packet msg,
293: Ajp13Packet outBuf) throws IOException {
294: // log("in handleLogComp :");
295:
296: byte[] rdigest = new byte[AJP14_ENTROPY_SEED_LEN];
297:
298: if (msg.getXBytes(rdigest, AJP14_ENTROPY_SEED_LEN) < 0)
299: return 200;
300:
301: String[] credentials = new String[2];
302: credentials[0] = getSeed();
303: credentials[1] = getPassword();
304: String computed = digest(credentials, "md5");
305: String received = new String(rdigest);
306:
307: // XXX temp workaround, to test the rest of the connector.
308:
309: if (!computed.equalsIgnoreCase(received)) {
310: log("in handleLogComp : authentification failure received="
311: + received + " awaited=" + computed);
312: }
313:
314: if (false) { // ! computed.equalsIgnoreCase(received)) {
315: log("in handleLogComp : authentification failure received="
316: + received + " awaited=" + computed);
317:
318: // we should have here a security mecanism which could maintain
319: // a list of remote IP which failed too many times
320: // so we could reject them quickly at next connect
321: outBuf.reset();
322: outBuf.appendByte(JK_AJP14_LOGNOK_CMD);
323: outBuf.appendLongInt(AJP14_BAD_KEY_ERR);
324: outBuf.end();
325: ch.send(outBuf);
326: return 200;
327: } else {
328: // logged we can go process requests
329: channel.setLogged(true);
330: outBuf.reset();
331: outBuf.appendByte(JK_AJP14_LOGOK_CMD);
332: outBuf
333: .appendLongInt(getProtocolFlags(webserverNegociation));
334: outBuf.appendString(containerSignature);
335: outBuf.end();
336: ch.send(outBuf);
337: }
338:
339: return (304);
340: }
341:
342: private int handleContextQuery(Ajp13 ch, Ajp13Packet msg,
343: Ajp13Packet outBuf) throws IOException {
344: log("in handleContextQuery :");
345: String virtualHost = msg.getString();
346: log("in handleContextQuery for virtual" + virtualHost);
347:
348: outBuf.reset();
349: outBuf.appendByte(JK_AJP14_CONTEXT_INFO_CMD);
350: outBuf.appendString(virtualHost);
351:
352: log("in handleContextQuery for virtual " + virtualHost
353: + "examples URI/MIMES");
354: outBuf.appendString("examples"); // first context - examples
355: outBuf.appendString("servlet/*"); // examples/servlet/*
356: outBuf.appendString("*.jsp"); // examples/*.jsp
357: outBuf.appendString(""); // no more URI/MIMES
358:
359: log("in handleContextQuery for virtual " + virtualHost
360: + "send admin URI/MIMES");
361: outBuf.appendString("admin"); // second context - admin
362: outBuf.appendString("servlet/*"); // /admin//servlet/*
363: outBuf.appendString("*.jsp"); // /admin/*.jsp
364: outBuf.appendString(""); // no more URI/MIMES
365:
366: outBuf.appendString(""); // no more contexts
367: outBuf.end();
368: ch.send(outBuf);
369:
370: return (304);
371: }
372:
373: private int handleStatus(Ajp13Packet msg, Ajp13Packet outBuf)
374: throws IOException {
375: log("in handleStatus :");
376: return (304);
377: }
378:
379: private int handleShutdown(Ajp13Packet msg, Ajp13Packet outBuf)
380: throws IOException {
381: log("in handleShutdown :");
382: return (304);
383: }
384:
385: private int handleContextState(Ajp13Packet msg, Ajp13Packet outBuf)
386: throws IOException {
387: log("in handleContextState :");
388: return (304);
389: }
390:
391: private int handleUnknowPacket(Ajp13Packet msg, Ajp13Packet outBuf)
392: throws IOException {
393: log("in handleUnknowPacket :");
394: return (304);
395: }
396:
397: // -------------------- Utils --------------------
398:
399: /**
400: * Compute the Protocol Negociation Flags
401: *
402: * Depending the protocol fatures implemented on servet-engine,
403: * we'll drop requested features which could be asked by web-server
404: *
405: * Hopefully this functions could be overrided by decendants
406: */
407: private int getProtocolFlags(int wanted) {
408: // no real-time context update
409: wanted &= ~(AJP14_CONTEXT_UPDATE_NEG |
410: // no gzip compression yet
411: AJP14_GZIP_STREAM_NEG |
412: // no DES56 cyphering yet
413: AJP14_DES56_STREAM_NEG |
414: // no Extended info on server SSL vars yet
415: AJP14_SSL_VSERVER_NEG |
416: // no Extended info on client SSL vars yet
417: AJP14_SSL_VCLIENT_NEG |
418: // no Extended info on crypto SSL vars yet
419: AJP14_SSL_VCRYPTO_NEG |
420: // no Extended info on misc SSL vars yet
421: AJP14_SSL_VMISC_NEG |
422: // Reset AJP protocol mask
423: AJP14_PROTO_SUPPORT_AJPXX_NEG);
424:
425: // Only strict AJP14 supported
426: return (wanted | AJP14_PROTO_SUPPORT_AJP14_NEG);
427: }
428:
429: /**
430: * Compute a digest (MD5 in AJP14) for an array of String
431: */
432: public final String digest(String[] credentials, String algorithm) {
433: try {
434: // Obtain a new message digest with MD5 encryption
435: MessageDigest md = (MessageDigest) MessageDigest
436: .getInstance(algorithm).clone();
437: // encode the credentials items
438: for (int i = 0; i < credentials.length; i++) {
439: if (debug > 0)
440: log("Credentials : " + i + " " + credentials[i]);
441: if (credentials[i] != null)
442: md.update(credentials[i].getBytes());
443: }
444: // obtain the byte array from the digest
445: byte[] dig = md.digest();
446: return HexUtils.convert(dig);
447: } catch (Exception ex) {
448: ex.printStackTrace();
449: return null;
450: }
451: }
452:
453: // -------------------- Debugging --------------------
454: // Very usefull for develoment
455:
456: /**
457: * Display Negociation field in human form
458: */
459: private String decodeNegociation(int nego) {
460: StringBuffer buf = new StringBuffer(128);
461:
462: if ((nego & AJP14_CONTEXT_INFO_NEG) != 0)
463: buf.append(" CONTEXT-INFO");
464:
465: if ((nego & AJP14_CONTEXT_UPDATE_NEG) != 0)
466: buf.append(" CONTEXT-UPDATE");
467:
468: if ((nego & AJP14_GZIP_STREAM_NEG) != 0)
469: buf.append(" GZIP-STREAM");
470:
471: if ((nego & AJP14_DES56_STREAM_NEG) != 0)
472: buf.append(" DES56-STREAM");
473:
474: if ((nego & AJP14_SSL_VSERVER_NEG) != 0)
475: buf.append(" SSL-VSERVER");
476:
477: if ((nego & AJP14_SSL_VCLIENT_NEG) != 0)
478: buf.append(" SSL-VCLIENT");
479:
480: if ((nego & AJP14_SSL_VCRYPTO_NEG) != 0)
481: buf.append(" SSL-VCRYPTO");
482:
483: if ((nego & AJP14_SSL_VMISC_NEG) != 0)
484: buf.append(" SSL-VMISC");
485:
486: if ((nego & AJP14_PROTO_SUPPORT_AJP14_NEG) != 0)
487: buf.append(" AJP14");
488:
489: if ((nego & AJP14_PROTO_SUPPORT_AJP15_NEG) != 0)
490: buf.append(" AJP15");
491:
492: if ((nego & AJP14_PROTO_SUPPORT_AJP16_NEG) != 0)
493: buf.append(" AJP16");
494:
495: return (buf.toString());
496: }
497:
498: private static int debug = 10;
499:
500: void log(String s) {
501: System.out.println("Ajp14Negotiation: " + s);
502: }
503: }
|