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