Source code: org/apache/ajp/tomcat4/config/ApacheConfig.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.tomcat4.config;
18
19 import java.io.File;
20 import java.io.FileWriter;
21 import java.io.IOException;
22 import java.io.PrintWriter;
23 import java.util.Date;
24 import java.util.Hashtable;
25
26 import org.apache.catalina.Context;
27 import org.apache.catalina.Host;
28
29 /* The idea is to keep all configuration in server.xml and
30 the normal apache config files. We don't want people to
31 touch apache ( or IIS, NES ) config files unless they
32 want to and know what they're doing ( better than we do :-).
33
34 One nice feature ( if someone sends it ) would be to
35 also edit httpd.conf to add the include.
36
37 We'll generate a number of configuration files - this one
38 is trying to generate a native apache config file.
39
40 Some web.xml mappings do not "map" to server configuration - in
41 this case we need to fallback to forward all requests to tomcat.
42
43 Ajp14 will add to that the posibility to have tomcat and
44 apache on different machines, and many other improvements -
45 but this should also work for Ajp12, Ajp13 and Jni.
46
47 */
48
49 /**
50 Generates automatic apache mod_jk configurations based on
51 the Tomcat server.xml settings and the war contexts
52 initialized during startup.
53 <p>
54 This config interceptor is enabled by inserting an ApacheConfig
55 <code>Listener</code> in
56 the server.xml file like so:
57 <pre>
58 * < Server ... >
59 * ...
60 * <Listener className=<b>org.apache.ajp.tomcat4.config.ApacheConfig</b>
61 * <i>options</i> />
62 * ...
63 * < /Server >
64 </pre>
65 where <i>options</i> can include any of the following attributes:
66 <ul>
67 <li><b>configHome</b> - default parent directory for the following paths.
68 If not set, this defaults to TOMCAT_HOME. Ignored
69 whenever any of the following paths is absolute.
70 </li>
71 <li><b>jkConfig</b> - path to use for writing Apache mod_jk conf file. If
72 not set, defaults to
73 "conf/auto/mod_jk.conf".</li>
74 <li><b>workersConfig</b> - path to workers.properties file used by
75 mod_jk. If not set, defaults to
76 "conf/jk/workers.properties".</li>
77 <li><b>modJk</b> - path to Apache mod_jk plugin file. If not set,
78 defaults to "modules/mod_jk.dll" on windows,
79 "modules/mod_jk.nlm" on netware, and
80 "libexec/mod_jk.so" everywhere else.</li>
81 <li><b>jkLog</b> - path to log file to be used by mod_jk.</li>
82 <li><b>jkDebug</b> - JK Loglevel setting. May be debug, info, error, or emerg.
83 If not set, defaults to emerg.</li>
84 <li><b>jkWorker</b> The desired worker. Must be set to one of the workers
85 defined in the workers.properties file. "ajp12", "ajp13"
86 or "inprocess" are the workers found in the default
87 workers.properties file. If not specified, defaults
88 to "ajp13" if an Ajp13Interceptor is in use, otherwise
89 it defaults to "ajp12".</li>
90 <li><b>forwardAll</b> - If true, forward all requests to Tomcat. This helps
91 insure that all the behavior configured in the web.xml
92 file functions correctly. If false, let Apache serve
93 static resources. The default is true.
94 Warning: When false, some configuration in
95 the web.xml may not be duplicated in Apache.
96 Review the mod_jk conf file to see what
97 configuration is actually being set in Apache.</li>
98 <li><b>noRoot</b> - If true, the root context is not mapped to
99 Tomcat. If false and forwardAll is true, all requests
100 to the root context are mapped to Tomcat. If false and
101 forwardAll is false, only JSP and servlets requests to
102 the root context are mapped to Tomcat. When false,
103 to correctly serve Tomcat's root context you must also
104 modify the DocumentRoot setting in Apache's httpd.conf
105 file to point to Tomcat's root context directory.
106 Otherwise some content, such as Apache's index.html,
107 will be served by Apache before mod_jk gets a chance
108 to claim the request and pass it to Tomcat.
109 The default is true.</li>
110 </ul>
111 <p>
112 @author Costin Manolache
113 @author Larry Isaacs
114 @author Mel Martinez
115 @author Bill Barker
116 */
117 public class ApacheConfig extends BaseJkConfig {
118
119 /** default path to mod_jk .conf location */
120 public static final String MOD_JK_CONFIG = "conf/auto/mod_jk.conf";
121 /** default path to workers.properties file
122 This should be also auto-generated from server.xml.
123 */
124 public static final String WORKERS_CONFIG = "conf/jk/workers.properties";
125 /** default mod_jk log file location */
126 public static final String JK_LOG_LOCATION = "logs/mod_jk.log";
127 /** default location of mod_jk Apache plug-in. */
128 public static final String MOD_JK;
129
130 //set up some defaults based on OS type
131 static{
132 String os = System.getProperty("os.name").toLowerCase();
133 if(os.indexOf("windows")>=0){
134 MOD_JK = "modules/mod_jk.dll";
135 }else if(os.indexOf("netware")>=0){
136 MOD_JK = "modules/mod_jk.nlm";
137 }else{
138 MOD_JK = "libexec/mod_jk.so";
139 }
140 }
141
142 private File jkConfig = null;
143 private File modJk = null;
144
145 // ssl settings
146 private boolean sslExtract=true;
147 private String sslHttpsIndicator="HTTPS";
148 private String sslSessionIndicator="SSL_SESSION_ID";
149 private String sslCipherIndicator="SSL_CIPHER";
150 private String sslCertsIndicator="SSL_CLIENT_CERT";
151
152 Hashtable NamedVirtualHosts=null;
153
154 public ApacheConfig() {
155 }
156
157 //-------------------- Properties --------------------
158
159 /**
160 set the path to the output file for the auto-generated
161 mod_jk configuration file. If this path is relative
162 then it will be resolved absolutely against
163 the getConfigHome() path.
164 <p>
165 @param path String path to a file
166 */
167 public void setJkConfig(String path){
168 jkConfig= (path==null)?null:new File(path);
169 }
170
171 /**
172 set the path to the mod_jk Apache Module
173 @param path String path to a file
174 */
175 public void setModJk(String path){
176 modJk=( path==null?null:new File(path));
177 }
178
179 /** By default mod_jk is configured to collect SSL information from
180 the apache environment and send it to the Tomcat workers. The
181 problem is that there are many SSL solutions for Apache and as
182 a result the environment variable names may change.
183
184 The following JK related SSL configureation
185 can be used to customize mod_jk's SSL behaviour.
186
187 Should mod_jk send SSL information to Tomact (default is On)
188 */
189 public void setExtractSSL( boolean sslMode ) {
190 this.sslExtract=sslMode;
191 }
192
193 /** What is the indicator for SSL (default is HTTPS)
194 */
195 public void setHttpsIndicator( String s ) {
196 sslHttpsIndicator=s;
197 }
198
199 /**What is the indicator for SSL session (default is SSL_SESSION_ID)
200 */
201 public void setSessionIndicator( String s ) {
202 sslSessionIndicator=s;
203 }
204
205 /**What is the indicator for client SSL cipher suit (default is SSL_CIPHER)
206 */
207 public void setCipherIndicator( String s ) {
208 sslCipherIndicator=s;
209 }
210
211 /** What is the indicator for the client SSL certificated(default
212 is SSL_CLIENT_CERT
213 */
214 public void setCertsIndicator( String s ) {
215 sslCertsIndicator=s;
216 }
217
218 // -------------------- Initialize/guess defaults --------------------
219
220 /** Initialize defaults for properties that are not set
221 explicitely
222 */
223 protected void initProperties() {
224 super.initProperties();
225
226 jkConfig= getConfigFile( jkConfig, configHome, MOD_JK_CONFIG);
227 workersConfig=getConfigFile( workersConfig, configHome,
228 WORKERS_CONFIG);
229 if( modJk == null )
230 modJk=new File(MOD_JK);
231 else
232 modJk=getConfigFile( modJk, configHome, MOD_JK );
233 jkLog=getConfigFile( jkLog, configHome, JK_LOG_LOCATION);
234 }
235 // -------------------- Generate config --------------------
236
237 protected PrintWriter getWriter() throws IOException {
238 String abJkConfig = jkConfig.getAbsolutePath();
239 return new PrintWriter(new FileWriter(abJkConfig, append));
240 }
241
242
243 // -------------------- Config sections --------------------
244
245 /** Generate the loadModule and general options
246 */
247 protected boolean generateJkHead(PrintWriter mod_jk)
248 {
249
250 mod_jk.println("########## Auto generated on " + new Date() +
251 "##########" );
252 mod_jk.println();
253
254 // Fail if mod_jk not found, let the user know the problem
255 // instead of running into problems later.
256 if( ! modJk.exists() ) {
257 log( "mod_jk location: " + modJk );
258 log( "Make sure it is installed corectly or " +
259 " set the config location" );
260 log( "Using <Listener className=\""+getClass().getName()+"\" modJk=\"PATH_TO_MOD_JK.SO_OR_DLL\" />" );
261 }
262
263 // Verify the file exists !!
264 mod_jk.println("<IfModule !mod_jk.c>");
265 mod_jk.println(" LoadModule jk_module \""+
266 modJk.toString().replace('\\','/') +
267 "\"");
268 mod_jk.println("</IfModule>");
269 mod_jk.println();
270
271
272 // Fail if workers file not found, let the user know the problem
273 // instead of running into problems later.
274 if( ! workersConfig.exists() ) {
275 log( "Can't find workers.properties at " + workersConfig );
276 log( "Please install it in the default location or " +
277 " set the config location" );
278 log( "Using <Listener className=\"" + getClass().getName() + "\" workersConfig=\"FULL_PATH\" />" );
279 return false;
280 }
281
282 mod_jk.println("JkWorkersFile \""
283 + workersConfig.toString().replace('\\', '/')
284 + "\"");
285
286 mod_jk.println("JkLogFile \""
287 + jkLog.toString().replace('\\', '/')
288 + "\"");
289 mod_jk.println();
290
291 if( jkDebug != null ) {
292 mod_jk.println("JkLogLevel " + jkDebug);
293 mod_jk.println();
294 }
295 return true;
296 }
297
298 protected void generateVhostHead(Host host, PrintWriter mod_jk) {
299
300 mod_jk.println();
301 String vhostip = host.getName();
302 String vhost = vhostip;
303 int ppos = vhost.indexOf(":");
304 if(ppos >= 0)
305 vhost = vhost.substring(0,ppos);
306
307 mod_jk.println("<VirtualHost "+ vhostip + ">");
308 mod_jk.println(" ServerName " + vhost );
309 String [] aliases=host.findAliases();
310 if( aliases.length > 0 ) {
311 mod_jk.print(" ServerAlias " );
312 for( int ii=0; ii < aliases.length ; ii++) {
313 mod_jk.print( aliases[ii] + " " );
314 }
315 mod_jk.println();
316 }
317 indent=" ";
318 }
319
320 protected void generateVhostTail(Host host, PrintWriter mod_jk) {
321 mod_jk.println("</VirtualHost>");
322 indent="";
323 }
324
325 protected void generateSSLConfig(PrintWriter mod_jk) {
326 if( ! sslExtract ) {
327 mod_jk.println("JkExtractSSL Off");
328 }
329 if( ! "HTTPS".equalsIgnoreCase( sslHttpsIndicator ) ) {
330 mod_jk.println("JkHTTPSIndicator " + sslHttpsIndicator);
331 }
332 if( ! "SSL_SESSION_ID".equalsIgnoreCase( sslSessionIndicator )) {
333 mod_jk.println("JkSESSIONIndicator " + sslSessionIndicator);
334 }
335 if( ! "SSL_CIPHER".equalsIgnoreCase( sslCipherIndicator )) {
336 mod_jk.println("JkCIPHERIndicator " + sslCipherIndicator);
337 }
338 if( ! "SSL_CLIENT_CERT".equalsIgnoreCase( sslCertsIndicator )) {
339 mod_jk.println("JkCERTSIndicator " + sslCertsIndicator);
340 }
341
342 mod_jk.println();
343 }
344
345 // -------------------- Forward all mode --------------------
346 String indent="";
347
348 /** Forward all requests for a context to tomcat.
349 The default.
350 */
351 protected void generateStupidMappings(Context context,
352 PrintWriter mod_jk )
353 {
354 String ctxPath = context.getPath();
355 if(ctxPath == null)
356 return;
357
358 String nPath=("".equals(ctxPath)) ? "/" : ctxPath;
359
360 mod_jk.println();
361 mod_jk.println(indent + "JkMount " + nPath + " " + jkWorker );
362 if( "".equals(ctxPath) ) {
363 mod_jk.println(indent + "JkMount " + nPath + "* " + jkWorker );
364 if ( context.getParent() instanceof Host ) {
365 mod_jk.println(indent + "DocumentRoot \"" +
366 getApacheDocBase(context) + "\"");
367 } else {
368 mod_jk.println(indent +
369 "# To avoid Apache serving root welcome files from htdocs, update DocumentRoot");
370 mod_jk.println(indent +
371 "# to point to: \"" + getApacheDocBase(context) + "\"");
372 }
373
374 } else {
375 mod_jk.println(indent + "JkMount " + nPath + "/* " + jkWorker );
376 }
377 }
378
379
380 private void generateNameVirtualHost( PrintWriter mod_jk, String ip ) {
381 if( !NamedVirtualHosts.containsKey(ip) ) {
382 mod_jk.println("NameVirtualHost " + ip + "");
383 NamedVirtualHosts.put(ip,ip);
384 }
385 }
386
387 // -------------------- Apache serves static mode --------------------
388 // This is not going to work for all apps. We fall back to stupid mode.
389
390 protected void generateContextMappings(Context context, PrintWriter mod_jk )
391 {
392 String ctxPath = context.getPath();
393 Host vhost = getHost(context);
394
395 if( noRoot && "".equals(ctxPath) ) {
396 log("Ignoring root context in non-forward-all mode ");
397 return;
398 }
399
400 mod_jk.println();
401 mod_jk.println(indent + "#################### " +
402 ((vhost!=null ) ? vhost.getName() + ":" : "" ) +
403 (("".equals(ctxPath)) ? "/" : ctxPath ) +
404 " ####################" );
405 mod_jk.println();
406 // Dynamic /servet pages go to Tomcat
407
408 generateStaticMappings( context, mod_jk );
409
410 // InvokerInterceptor - it doesn't have a container,
411 // but it's implemented using a special module.
412
413 // XXX we need to better collect all mappings
414
415 if(context.getLoginConfig() != null) {
416 String loginPage = context.getLoginConfig().getLoginPage();
417 if(loginPage != null) {
418 int lpos = loginPage.lastIndexOf("/");
419 String jscurl = loginPage.substring(0,lpos+1) + "j_security_check";
420 addMapping( ctxPath, jscurl, mod_jk);
421 }
422 }
423 String [] servletMaps = context.findServletMappings();
424 for(int ii=0; ii < servletMaps.length; ii++) {
425 addMapping( ctxPath, servletMaps[ii] , mod_jk );
426 }
427 }
428
429 /** Add an Apache extension mapping.
430 */
431 protected boolean addExtensionMapping( String ctxPath, String ext,
432 PrintWriter mod_jk )
433 {
434 if( debug > 0 )
435 log( "Adding extension map for " + ctxPath + "/*." + ext );
436 mod_jk.println(indent + "JkMount " + ctxPath + "/*." + ext
437 + " " + jkWorker);
438 return true;
439 }
440
441
442 /** Add a fulling specified Appache mapping.
443 */
444 protected boolean addMapping( String fullPath, PrintWriter mod_jk ) {
445 if( debug > 0 )
446 log( "Adding map for " + fullPath );
447 mod_jk.println(indent + "JkMount " + fullPath + " " + jkWorker );
448 return true;
449 }
450 /** Add a partially specified Appache mapping.
451 */
452 protected boolean addMapping( String ctxP, String ext, PrintWriter mod_jk ) {
453 if( debug > 0 )
454 log( "Adding map for " + ext );
455 if(! ext.startsWith("/") )
456 ext = "/" + ext;
457 if(ext.length() > 1)
458 mod_jk.println(indent + "JkMount " + ctxP + ext+ " " + jkWorker );
459 return true;
460 }
461
462 private void generateWelcomeFiles(Context context, PrintWriter mod_jk ) {
463 String wf[]=context.findWelcomeFiles();
464 if( wf==null || wf.length == 0 )
465 return;
466 mod_jk.print(indent + " DirectoryIndex ");
467 for( int i=0; i<wf.length ; i++ ) {
468 mod_jk.print( wf[i] + " " );
469 }
470 mod_jk.println();
471 }
472
473 /** Mappings for static content. XXX need to add welcome files,
474 * mime mappings ( all will be handled by Mime and Static modules of
475 * apache ).
476 */
477 private void generateStaticMappings(Context context, PrintWriter mod_jk ) {
478 String ctxPath = context.getPath();
479
480 // Calculate the absolute path of the document base
481 String docBase = getApacheDocBase(context);
482
483 if( !"".equals(ctxPath) ) {
484 // Static files will be served by Apache
485 mod_jk.println(indent + "# Static files ");
486 mod_jk.println(indent + "Alias " + ctxPath + " \"" + docBase + "\"");
487 mod_jk.println();
488 } else {
489 if ( getHost(context) != null ) {
490 mod_jk.println(indent + "DocumentRoot \"" +
491 getApacheDocBase(context) + "\"");
492 } else {
493 // For root context, ask user to update DocumentRoot setting.
494 // Using "Alias / " interferes with the Alias for other contexts.
495 mod_jk.println(indent +
496 "# Be sure to update DocumentRoot");
497 mod_jk.println(indent +
498 "# to point to: \"" + docBase + "\"");
499 }
500 }
501 mod_jk.println(indent + "<Directory \"" + docBase + "\">");
502 mod_jk.println(indent + " Options Indexes FollowSymLinks");
503
504 generateWelcomeFiles(context, mod_jk);
505
506 // XXX XXX Here goes the Mime types and welcome files !!!!!!!!
507 mod_jk.println(indent + "</Directory>");
508 mod_jk.println();
509
510
511 // Deny serving any files from WEB-INF
512 mod_jk.println();
513 mod_jk.println(indent +
514 "# Deny direct access to WEB-INF and META-INF");
515 mod_jk.println(indent + "#");
516 mod_jk.println(indent + "<Location \"" + ctxPath + "/WEB-INF/*\">");
517 mod_jk.println(indent + " AllowOverride None");
518 mod_jk.println(indent + " deny from all");
519 mod_jk.println(indent + "</Location>");
520 // Deny serving any files from META-INF
521 mod_jk.println();
522 mod_jk.println(indent + "<Location \"" + ctxPath + "/META-INF/*\">");
523 mod_jk.println(indent + " AllowOverride None");
524 mod_jk.println(indent + " deny from all");
525 mod_jk.println(indent + "</Location>");
526 if (File.separatorChar == '\\') {
527 mod_jk.println(indent + "#");
528 mod_jk.println(indent +
529 "# Use Directory too. On Windows, Location doesn't"
530 + " work unless case matches");
531 mod_jk.println(indent + "#");
532 mod_jk.println(indent +
533 "<Directory \"" + docBase + "/WEB-INF/\">");
534 mod_jk.println(indent + " AllowOverride None");
535 mod_jk.println(indent + " deny from all");
536 mod_jk.println(indent + "</Directory>");
537 mod_jk.println();
538 mod_jk.println(indent +
539 "<Directory \"" + docBase + "/META-INF/\">");
540 mod_jk.println(indent + " AllowOverride None");
541 mod_jk.println(indent + " deny from all");
542 mod_jk.println(indent + "</Directory>");
543 }
544 mod_jk.println();
545 }
546
547 // -------------------- Utils --------------------
548
549 private String getApacheDocBase(Context context)
550 {
551 // Calculate the absolute path of the document base
552 String docBase = getAbsoluteDocBase(context);
553 if (File.separatorChar == '\\') {
554 // use separator preferred by Apache
555 docBase = docBase.replace('\\','/');
556 }
557 return docBase;
558 }
559
560 private String getVirtualHostAddress(String vhost, String vhostip) {
561 if( vhostip == null ) {
562 if ( vhost != null && vhost.length() > 0 && Character.isDigit(vhost.charAt(0)) )
563 vhostip=vhost;
564 else
565 vhostip="*";
566 }
567 return vhostip;
568 }
569
570 }