Source code: org/apache/ajp/NegociationHandler.java
1 /*
2 * Copyright 1999-2004 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.apache.ajp;
18
19 import java.io.IOException;
20 import java.security.MessageDigest;
21
22 import org.apache.tomcat.util.buf.HexUtils;
23 import org.apache.tomcat.util.http.BaseRequest;
24
25
26 /**
27 * Handler for the protocol negotiation. It will authenticate and
28 * exchange information about supported messages on each end.
29 *
30 *
31 * @author Henri Gomez [hgomez@apache.org]
32 * @author Dan Milstein [danmil@shore.net]
33 * @author Keith Wannamaker [Keith@Wannamaker.org]
34 * @author Costin Manolache
35 */
36 public class NegociationHandler extends AjpHandler
37 {
38
39 private static org.apache.commons.logging.Log log=
40 org.apache.commons.logging.LogFactory.getLog(NegociationHandler.class );
41
42 // Initial Login Phase (web server -> servlet engine)
43 public static final byte JK_AJP14_LOGINIT_CMD=0x10;
44
45 // Second Login Phase (servlet engine -> web server), md5 seed is received
46 public static final byte JK_AJP14_LOGSEED_CMD=0x11;
47
48 // Third Login Phase (web server -> servlet engine),
49 // md5 of seed + secret is sent
50 public static final byte JK_AJP14_LOGCOMP_CMD=0x12;
51
52 // Login Accepted (servlet engine -> web server)
53 public static final byte JK_AJP14_LOGOK_CMD=0x13;
54
55 // Login Rejected (servlet engine -> web server), will be logged
56 public static final byte JK_AJP14_LOGNOK_CMD=0x14;
57
58 // Context Query (web server -> servlet engine),
59 // which URI are handled by servlet engine ?
60 public static final byte JK_AJP14_CONTEXT_QRY_CMD=0x15;
61
62 // Context Info (servlet engine -> web server), URI handled response
63 public static final byte JK_AJP14_CONTEXT_INFO_CMD= 0x16;
64
65 // Context Update (servlet engine -> web server), status of context changed
66 public static final byte JK_AJP14_CONTEXT_UPDATE_CMD= 0x17;
67
68 // Servlet Engine Status (web server -> servlet engine),
69 // what's the status of the servlet engine ?
70 public static final byte JK_AJP14_STATUS_CMD= 0x18;
71
72 // Secure Shutdown command (web server -> servlet engine),
73 //please servlet stop yourself.
74 public static final byte JK_AJP14_SHUTDOWN_CMD= 0x19;
75
76 // Secure Shutdown command Accepted (servlet engine -> web server)
77 public static final byte JK_AJP14_SHUTOK_CMD= 0x1A;
78
79 // Secure Shutdown Rejected (servlet engine -> web server)
80 public static final byte JK_AJP14_SHUTNOK_CMD= 0x1B;
81
82 // Context Status (web server -> servlet engine),
83 //what's the status of the context ?
84 public static final byte JK_AJP14_CONTEXT_STATE_CMD= 0x1C;
85
86 // Context Status Reply (servlet engine -> web server), status of context
87 public static final byte JK_AJP14_CONTEXT_STATE_REP_CMD = 0x1D;
88
89 // Unknown Packet Reply (web server <-> servlet engine),
90 //when a packet couldn't be decoded
91 public static final byte JK_AJP14_UNKNOW_PACKET_CMD = 0x1E;
92
93
94 // -------------------- Other constants --------------------
95
96 // Entropy Packet Size
97 public static final int AJP14_ENTROPY_SEED_LEN= 32;
98 public static final int AJP14_COMPUTED_KEY_LEN= 32;
99
100 // web-server want context info after login
101 public static final int AJP14_CONTEXT_INFO_NEG= 0x80000000;
102
103 // web-server want context updates
104 public static final int AJP14_CONTEXT_UPDATE_NEG= 0x40000000;
105
106 // web-server want compressed stream
107 public static final int AJP14_GZIP_STREAM_NEG= 0x20000000;
108
109 // web-server want crypted DES56 stream with secret key
110 public static final int AJP14_DES56_STREAM_NEG= 0x10000000;
111
112 // Extended info on server SSL vars
113 public static final int AJP14_SSL_VSERVER_NEG= 0x08000000;
114
115 // Extended info on client SSL vars
116 public static final int AJP14_SSL_VCLIENT_NEG= 0x04000000;
117
118 // Extended info on crypto SSL vars
119 public static final int AJP14_SSL_VCRYPTO_NEG= 0x02000000;
120
121 // Extended info on misc SSL vars
122 public static final int AJP14_SSL_VMISC_NEG= 0x01000000;
123
124 // mask of protocol supported
125 public static final int AJP14_PROTO_SUPPORT_AJPXX_NEG=0x00FF0000;
126
127 // communication could use AJP14
128 public static final int AJP14_PROTO_SUPPORT_AJP14_NEG=0x00010000;
129
130 // communication could use AJP15
131 public static final int AJP14_PROTO_SUPPORT_AJP15_NEG=0x00020000;
132
133 // communication could use AJP16
134 public static final int AJP14_PROTO_SUPPORT_AJP16_NEG=0x00040000;
135
136 // Some failure codes
137 public static final int AJP14_BAD_KEY_ERR= 0xFFFFFFFF;
138 public static final int AJP14_ENGINE_DOWN_ERR = 0xFFFFFFFE;
139 public static final int AJP14_RETRY_LATER_ERR = 0xFFFFFFFD;
140 public static final int AJP14_SHUT_AUTHOR_FAILED_ERR = 0xFFFFFFFC;
141
142 // Some status codes
143 public static final byte AJP14_CONTEXT_DOWN= 0x01;
144 public static final byte AJP14_CONTEXT_UP= 0x02;
145 public static final byte AJP14_CONTEXT_OK= 0x03;
146
147
148 // -------------------- Parameters --------------------
149 String containerSignature="Ajp14-based container";
150 String seed="seed";// will use random
151 String password;
152
153 int webserverNegociation=0;
154 // String webserverName;
155
156 public NegociationHandler() {
157 setSeed("myveryrandomentropy");
158 setPassword("myverysecretkey");
159 }
160
161 public void setContainerSignature( String s ) {
162 containerSignature=s;
163 }
164
165 // -------------------- State --------------------
166
167 // public String getWebserverName() {
168 // return webserverName;
169 // }
170
171 // -------------------- Parameters --------------------
172
173 /**
174 * Set the original entropy seed
175 */
176 public void setSeed(String pseed)
177 {
178 String[] credentials = new String[1];
179 credentials[0] = pseed;
180 seed = digest(credentials, "md5");
181 }
182
183 /**
184 * Get the original entropy seed
185 */
186 public String getSeed()
187 {
188 return seed;
189 }
190
191 /**
192 * Set the secret password
193 */
194 public void setPassword(String ppwd)
195 {
196 password = ppwd;
197 }
198
199 /**
200 * Get the secret password
201 */
202 public String getPassword()
203 {
204 return password;
205 }
206
207 // -------------------- Initialization --------------------
208
209 public void init( Ajp13 ajp14 ) {
210 super.init(ajp14);
211 // register incoming message handlers
212 ajp14.registerMessageType( JK_AJP14_LOGINIT_CMD,"JK_AJP14_LOGINIT_CMD",
213 this, null); //
214 ajp14.registerMessageType( JK_AJP14_LOGCOMP_CMD,"JK_AJP14_LOGCOMP_CMD",
215 this, null); //
216 ajp14.registerMessageType( RequestHandler.JK_AJP13_SHUTDOWN,"JK_AJP13_SHUTDOWN",
217 this, null); //
218 ajp14.registerMessageType( JK_AJP14_CONTEXT_QRY_CMD,
219 "JK_AJP14_CONTEXT_QRY_CMD",
220 this, null); //
221 ajp14.registerMessageType( JK_AJP14_STATUS_CMD,"JK_AJP14_STATUS_CMD",
222 this, null); //
223 ajp14.registerMessageType( JK_AJP14_SHUTDOWN_CMD,
224 "JK_AJP14_SHUTDOWN_CMD",
225 this, null); //
226 ajp14.registerMessageType( JK_AJP14_CONTEXT_STATE_CMD,
227 "JK_AJP14_CONTEXT_STATE_CMD",
228 this, null); //
229 ajp14.registerMessageType( JK_AJP14_UNKNOW_PACKET_CMD,
230 "JK_AJP14_UNKNOW_PACKET_CMD",
231 this, null); //
232
233 // register outgoing messages handler
234 ajp14.registerMessageType( JK_AJP14_LOGNOK_CMD,"JK_AJP14_LOGNOK_CMD",
235 this,null );
236
237 }
238
239
240
241 // -------------------- Dispatch --------------------
242
243 public int handleAjpMessage( int type, Ajp13 ch, Ajp13Packet hBuf,
244 BaseRequest req )
245 throws IOException
246 {
247 if (log.isDebugEnabled())
248 log.debug("handleAjpMessage: " + type );
249 Ajp13Packet outBuf=ch.outBuf;
250 // Valid requests when not logged:
251 switch( type ) {
252 case JK_AJP14_LOGINIT_CMD :
253 return handleLogInit(ch, hBuf, outBuf);
254 case JK_AJP14_LOGCOMP_CMD :
255 return handleLogComp(ch, hBuf, outBuf);
256 case RequestHandler.JK_AJP13_SHUTDOWN:
257 return -2;
258 case JK_AJP14_CONTEXT_QRY_CMD :
259 return handleContextQuery(ch, hBuf, outBuf);
260 case JK_AJP14_STATUS_CMD :
261 return handleStatus(hBuf, outBuf);
262 case JK_AJP14_SHUTDOWN_CMD :
263 return handleShutdown(hBuf, outBuf);
264 case JK_AJP14_CONTEXT_STATE_CMD :
265 return handleContextState(hBuf, outBuf);
266 case JK_AJP14_UNKNOW_PACKET_CMD :
267 return handleUnknowPacket(hBuf, outBuf);
268 default:
269 log("unknown command " + type + " received");
270 return 200; // XXX This is actually an error condition
271 }
272 //return UNKNOWN;
273 }
274
275 //----------- Implementation for various protocol commands -----------
276
277 /**
278 * Handle the Initial Login Message from Web-Server
279 *
280 * Get the requested Negociation Flags
281 * Get also the Web-Server Name
282 *
283 * Send Login Seed (MD5 of seed)
284 */
285 private int handleLogInit( Ajp13 ch, Ajp13Packet msg,
286 Ajp13Packet outBuf )
287 throws IOException
288 {
289 webserverNegociation = msg.getLongInt();
290 String webserverName = msg.getString();
291 log("in handleLogInit with nego " +
292 decodeNegociation(webserverNegociation) +
293 " from webserver " + webserverName);
294
295 outBuf.reset();
296 outBuf.appendByte(JK_AJP14_LOGSEED_CMD);
297 String[] credentials = new String[1];
298 credentials[0] = getSeed();
299 outBuf.appendXBytes(getSeed().getBytes(), 0, AJP14_ENTROPY_SEED_LEN);
300 log("in handleLogInit: sent entropy " + getSeed());
301 outBuf.end();
302 ch.send(outBuf);
303 return 304;
304 }
305
306 /**
307 * Handle the Second Phase of Login (accreditation)
308 *
309 * Get the MD5 digest of entropy + secret password
310 * If the authentification is valid send back LogOk
311 * If the authentification failed send back LogNok
312 */
313 private int handleLogComp( Ajp13 ch, Ajp13Packet msg,
314 Ajp13Packet outBuf )
315 throws IOException
316 {
317 // log("in handleLogComp :");
318
319 byte [] rdigest = new byte[AJP14_ENTROPY_SEED_LEN];
320
321 if (msg.getXBytes(rdigest, AJP14_ENTROPY_SEED_LEN) < 0)
322 return 200;
323
324 String[] credentials = new String[2];
325 credentials[0] = getSeed();
326 credentials[1] = getPassword();
327 String computed = digest(credentials, "md5");
328 String received = new String(rdigest);
329
330 // XXX temp workaround, to test the rest of the connector.
331
332 if ( ! computed.equalsIgnoreCase(received)) {
333 log("in handleLogComp : authentification failure received=" +
334 received + " awaited=" + computed);
335 }
336
337 if (false ) { // ! computed.equalsIgnoreCase(received)) {
338 log("in handleLogComp : authentification failure received=" +
339 received + " awaited=" + computed);
340
341 // we should have here a security mecanism which could maintain
342 // a list of remote IP which failed too many times
343 // so we could reject them quickly at next connect
344 outBuf.reset();
345 outBuf.appendByte(JK_AJP14_LOGNOK_CMD);
346 outBuf.appendLongInt(AJP14_BAD_KEY_ERR);
347 outBuf.end();
348 ch.send(outBuf);
349 return 200;
350 } else {
351 // logged we can go process requests
352 channel.setLogged(true);
353 outBuf.reset();
354 outBuf.appendByte(JK_AJP14_LOGOK_CMD);
355 outBuf.appendLongInt(getProtocolFlags(webserverNegociation));
356 outBuf.appendString( containerSignature );
357 outBuf.end();
358 ch.send(outBuf);
359 }
360
361 return (304);
362 }
363
364 private int handleContextQuery( Ajp13 ch, Ajp13Packet msg,
365 Ajp13Packet outBuf )
366 throws IOException
367 {
368 log("in handleContextQuery :");
369 String virtualHost = msg.getString();
370 log("in handleContextQuery for virtual" + virtualHost);
371
372 outBuf.reset();
373 outBuf.appendByte(JK_AJP14_CONTEXT_INFO_CMD);
374 outBuf.appendString( virtualHost );
375
376 log("in handleContextQuery for virtual " + virtualHost +
377 "examples URI/MIMES");
378 outBuf.appendString("examples"); // first context - examples
379 outBuf.appendString("servlet/*"); // examples/servlet/*
380 outBuf.appendString("*.jsp"); // examples/*.jsp
381 outBuf.appendString(""); // no more URI/MIMES
382
383 log("in handleContextQuery for virtual " + virtualHost +
384 "send admin URI/MIMES");
385 outBuf.appendString("admin"); // second context - admin
386 outBuf.appendString("servlet/*"); // /admin//servlet/*
387 outBuf.appendString("*.jsp"); // /admin/*.jsp
388 outBuf.appendString(""); // no more URI/MIMES
389
390 outBuf.appendString(""); // no more contexts
391 outBuf.end();
392 ch.send(outBuf);
393
394 return (304);
395 }
396
397 private int handleStatus( Ajp13Packet msg, Ajp13Packet outBuf )
398 throws IOException
399 {
400 log("in handleStatus :");
401 return (304);
402 }
403
404 private int handleShutdown( Ajp13Packet msg, Ajp13Packet outBuf )
405 throws IOException
406 {
407 log("in handleShutdown :");
408 return (304);
409 }
410
411 private int handleContextState( Ajp13Packet msg , Ajp13Packet outBuf)
412 throws IOException
413 {
414 log("in handleContextState :");
415 return (304);
416 }
417
418 private int handleUnknowPacket( Ajp13Packet msg, Ajp13Packet outBuf )
419 throws IOException
420 {
421 log("in handleUnknowPacket :");
422 return (304);
423 }
424
425 // -------------------- Utils --------------------
426
427 /**
428 * Compute the Protocol Negociation Flags
429 *
430 * Depending the protocol fatures implemented on servet-engine,
431 * we'll drop requested features which could be asked by web-server
432 *
433 * Hopefully this functions could be overrided by decendants
434 */
435 private int getProtocolFlags(int wanted)
436 {
437 // no real-time context update
438 wanted &= ~(AJP14_CONTEXT_UPDATE_NEG |
439 // no gzip compression yet
440 AJP14_GZIP_STREAM_NEG |
441 // no DES56 cyphering yet
442 AJP14_DES56_STREAM_NEG |
443 // no Extended info on server SSL vars yet
444 AJP14_SSL_VSERVER_NEG |
445 // no Extended info on client SSL vars yet
446 AJP14_SSL_VCLIENT_NEG |
447 // no Extended info on crypto SSL vars yet
448 AJP14_SSL_VCRYPTO_NEG |
449 // no Extended info on misc SSL vars yet
450 AJP14_SSL_VMISC_NEG |
451 // Reset AJP protocol mask
452 AJP14_PROTO_SUPPORT_AJPXX_NEG);
453
454 // Only strict AJP14 supported
455 return (wanted | AJP14_PROTO_SUPPORT_AJP14_NEG);
456 }
457
458 /**
459 * Compute a digest (MD5 in AJP14) for an array of String
460 */
461 public final String digest(String[] credentials, String algorithm) {
462 try {
463 // Obtain a new message digest with MD5 encryption
464 MessageDigest md =
465 (MessageDigest)MessageDigest.getInstance(algorithm).clone();
466 // encode the credentials items
467 for (int i = 0; i < credentials.length; i++) {
468 if( debug > 0 )
469 log("Credentials : " + i + " " + credentials[i]);
470 if( credentials[i] != null )
471 md.update(credentials[i].getBytes());
472 }
473 // obtain the byte array from the digest
474 byte[] dig = md.digest();
475 return HexUtils.convert(dig);
476 } catch (Exception ex) {
477 ex.printStackTrace();
478 return null;
479 }
480 }
481
482 // -------------------- Debugging --------------------
483 // Very usefull for develoment
484
485 /**
486 * Display Negociation field in human form
487 */
488 private String decodeNegociation(int nego)
489 {
490 StringBuffer buf = new StringBuffer(128);
491
492 if ((nego & AJP14_CONTEXT_INFO_NEG) != 0)
493 buf.append(" CONTEXT-INFO");
494
495 if ((nego & AJP14_CONTEXT_UPDATE_NEG) != 0)
496 buf.append(" CONTEXT-UPDATE");
497
498 if ((nego & AJP14_GZIP_STREAM_NEG) != 0)
499 buf.append(" GZIP-STREAM");
500
501 if ((nego & AJP14_DES56_STREAM_NEG) != 0)
502 buf.append(" DES56-STREAM");
503
504 if ((nego & AJP14_SSL_VSERVER_NEG) != 0)
505 buf.append(" SSL-VSERVER");
506
507 if ((nego & AJP14_SSL_VCLIENT_NEG) != 0)
508 buf.append(" SSL-VCLIENT");
509
510 if ((nego & AJP14_SSL_VCRYPTO_NEG) != 0)
511 buf.append(" SSL-VCRYPTO");
512
513 if ((nego & AJP14_SSL_VMISC_NEG) != 0)
514 buf.append(" SSL-VMISC");
515
516 if ((nego & AJP14_PROTO_SUPPORT_AJP14_NEG) != 0)
517 buf.append(" AJP14");
518
519 if ((nego & AJP14_PROTO_SUPPORT_AJP15_NEG) != 0)
520 buf.append(" AJP15");
521
522 if ((nego & AJP14_PROTO_SUPPORT_AJP16_NEG) != 0)
523 buf.append(" AJP16");
524
525 return (buf.toString());
526 }
527
528 private static int debug=10;
529 void log(String s) {
530 if (log.isDebugEnabled())
531 log.debug("Ajp14Negotiation: " + s );
532 }
533 }