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.jk.config;
018:
019: import java.io.File;
020: import java.io.FileWriter;
021: import java.io.IOException;
022: import java.io.PrintWriter;
023: import java.util.Date;
024: import java.util.Hashtable;
025:
026: import org.apache.catalina.Context;
027: import org.apache.catalina.Host;
028:
029: /* The idea is to keep all configuration in server.xml and
030: the normal apache config files. We don't want people to
031: touch apache ( or IIS, NES ) config files unless they
032: want to and know what they're doing ( better than we do :-).
033:
034: One nice feature ( if someone sends it ) would be to
035: also edit httpd.conf to add the include.
036:
037: We'll generate a number of configuration files - this one
038: is trying to generate a native apache config file.
039:
040: Some web.xml mappings do not "map" to server configuration - in
041: this case we need to fallback to forward all requests to tomcat.
042:
043: Ajp14 will add to that the posibility to have tomcat and
044: apache on different machines, and many other improvements -
045: but this should also work for Ajp12, Ajp13 and Jni.
046:
047: */
048:
049: /**
050: Generates automatic apache mod_jk configurations based on
051: the Tomcat server.xml settings and the war contexts
052: initialized during startup.
053: <p>
054: This config interceptor is enabled by inserting an ApacheConfig
055: <code>Listener</code> in
056: the server.xml file like so:
057: <pre>
058: * < Server ... >
059: * ...
060: * <Listener className=<b>org.apache.ajp.tomcat4.config.ApacheConfig</b>
061: * <i>options</i> />
062: * ...
063: * < /Server >
064: </pre>
065: where <i>options</i> can include any of the following attributes:
066: <ul>
067: <li><b>configHome</b> - default parent directory for the following paths.
068: If not set, this defaults to TOMCAT_HOME. Ignored
069: whenever any of the following paths is absolute.
070: </li>
071: <li><b>jkConfig</b> - path to use for writing Apache mod_jk conf file. If
072: not set, defaults to
073: "conf/auto/mod_jk.conf".</li>
074: <li><b>workersConfig</b> - path to workers.properties file used by
075: mod_jk. If not set, defaults to
076: "conf/jk/workers.properties".</li>
077: <li><b>modJk</b> - path to Apache mod_jk plugin file. If not set,
078: defaults to "modules/mod_jk.dll" on windows,
079: "modules/mod_jk.nlm" on netware, and
080: "libexec/mod_jk.so" everywhere else.</li>
081: <li><b>jkLog</b> - path to log file to be used by mod_jk.</li>
082: <li><b>jkDebug</b> - JK Loglevel setting. May be debug, info, error, or emerg.
083: If not set, defaults to emerg.</li>
084: <li><b>jkWorker</b> The desired worker. Must be set to one of the workers
085: defined in the workers.properties file. "ajp12", "ajp13"
086: or "inprocess" are the workers found in the default
087: workers.properties file. If not specified, defaults
088: to "ajp13" if an Ajp13Interceptor is in use, otherwise
089: it defaults to "ajp12".</li>
090: <li><b>forwardAll</b> - If true, forward all requests to Tomcat. This helps
091: insure that all the behavior configured in the web.xml
092: file functions correctly. If false, let Apache serve
093: static resources. The default is true.
094: Warning: When false, some configuration in
095: the web.xml may not be duplicated in Apache.
096: Review the mod_jk conf file to see what
097: configuration is actually being set in Apache.</li>
098: <li><b>noRoot</b> - If true, the root context is not mapped to
099: 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 <b>path</b> 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 <b>path</b> 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:
236: // -------------------- Generate config --------------------
237:
238: protected PrintWriter getWriter() throws IOException {
239: String abJkConfig = jkConfig.getAbsolutePath();
240: return new PrintWriter(new FileWriter(abJkConfig, append));
241: }
242:
243: // -------------------- Config sections --------------------
244:
245: /** Generate the loadModule and general options
246: */
247: protected boolean generateJkHead(PrintWriter mod_jk) {
248:
249: mod_jk.println("########## Auto generated on " + new Date()
250: + "##########");
251: mod_jk.println();
252:
253: // Fail if mod_jk not found, let the user know the problem
254: // instead of running into problems later.
255: if (!modJk.exists()) {
256: log("mod_jk location: " + modJk);
257: log("Make sure it is installed corectly or "
258: + " set the config location");
259: log("Using <Listener className=\"" + getClass().getName()
260: + "\" 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: mod_jk.println("</IfModule>");
268: mod_jk.println();
269:
270: // Fail if workers file not found, let the user know the problem
271: // instead of running into problems later.
272: if (!workersConfig.exists()) {
273: log("Can't find workers.properties at " + workersConfig);
274: log("Please install it in the default location or "
275: + " set the config location");
276: log("Using <Listener className=\"" + getClass().getName()
277: + "\" workersConfig=\"FULL_PATH\" />");
278: return false;
279: }
280:
281: mod_jk.println("JkWorkersFile \""
282: + workersConfig.toString().replace('\\', '/') + "\"");
283:
284: mod_jk.println("JkLogFile \""
285: + jkLog.toString().replace('\\', '/') + "\"");
286: mod_jk.println();
287:
288: if (jkDebug != null) {
289: mod_jk.println("JkLogLevel " + jkDebug);
290: mod_jk.println();
291: }
292: return true;
293: }
294:
295: protected void generateVhostHead(Host host, PrintWriter mod_jk) {
296:
297: mod_jk.println();
298: String vhostip = host.getName();
299: String vhost = vhostip;
300: int ppos = vhost.indexOf(":");
301: if (ppos >= 0)
302: vhost = vhost.substring(0, ppos);
303:
304: mod_jk.println("<VirtualHost " + vhostip + ">");
305: mod_jk.println(" ServerName " + vhost);
306: String[] aliases = host.findAliases();
307: if (aliases.length > 0) {
308: mod_jk.print(" ServerAlias ");
309: for (int ii = 0; ii < aliases.length; ii++) {
310: mod_jk.print(aliases[ii] + " ");
311: }
312: mod_jk.println();
313: }
314: indent = " ";
315: }
316:
317: protected void generateVhostTail(Host host, PrintWriter mod_jk) {
318: mod_jk.println("</VirtualHost>");
319: indent = "";
320: }
321:
322: protected void generateSSLConfig(PrintWriter mod_jk) {
323: if (!sslExtract) {
324: mod_jk.println("JkExtractSSL Off");
325: }
326: if (!"HTTPS".equalsIgnoreCase(sslHttpsIndicator)) {
327: mod_jk.println("JkHTTPSIndicator " + sslHttpsIndicator);
328: }
329: if (!"SSL_SESSION_ID".equalsIgnoreCase(sslSessionIndicator)) {
330: mod_jk.println("JkSESSIONIndicator " + sslSessionIndicator);
331: }
332: if (!"SSL_CIPHER".equalsIgnoreCase(sslCipherIndicator)) {
333: mod_jk.println("JkCIPHERIndicator " + sslCipherIndicator);
334: }
335: if (!"SSL_CLIENT_CERT".equalsIgnoreCase(sslCertsIndicator)) {
336: mod_jk.println("JkCERTSIndicator " + sslCertsIndicator);
337: }
338:
339: mod_jk.println();
340: }
341:
342: // -------------------- Forward all mode --------------------
343: String indent = "";
344:
345: /** Forward all requests for a context to tomcat.
346: The default.
347: */
348: protected void generateStupidMappings(Context context,
349: PrintWriter mod_jk) {
350: String ctxPath = context.getPath();
351: if (ctxPath == null)
352: return;
353:
354: String nPath = ("".equals(ctxPath)) ? "/" : ctxPath;
355:
356: mod_jk.println();
357: mod_jk.println(indent + "JkMount " + nPath + " " + jkWorker);
358: if ("".equals(ctxPath)) {
359: mod_jk.println(indent + "JkMount " + nPath + "* "
360: + jkWorker);
361: if (context.getParent() instanceof Host) {
362: mod_jk.println(indent + "DocumentRoot \""
363: + getApacheDocBase(context) + "\"");
364: } else {
365: mod_jk
366: .println(indent
367: + "# To avoid Apache serving root welcome files from htdocs, update DocumentRoot");
368: mod_jk.println(indent + "# to point to: \""
369: + getApacheDocBase(context) + "\"");
370: }
371:
372: } else {
373: mod_jk.println(indent + "JkMount " + nPath + "/* "
374: + jkWorker);
375: }
376: }
377:
378: private void generateNameVirtualHost(PrintWriter mod_jk, String ip) {
379: if (!NamedVirtualHosts.containsKey(ip)) {
380: mod_jk.println("NameVirtualHost " + ip + "");
381: NamedVirtualHosts.put(ip, ip);
382: }
383: }
384:
385: // -------------------- Apache serves static mode --------------------
386: // This is not going to work for all apps. We fall back to stupid mode.
387:
388: protected void generateContextMappings(Context context,
389: PrintWriter mod_jk) {
390: String ctxPath = context.getPath();
391: Host vhost = getHost(context);
392:
393: if (noRoot && "".equals(ctxPath)) {
394: log("Ignoring root context in non-forward-all mode ");
395: return;
396: }
397:
398: mod_jk.println();
399: mod_jk.println(indent + "#################### "
400: + ((vhost != null) ? vhost.getName() + ":" : "")
401: + (("".equals(ctxPath)) ? "/" : ctxPath)
402: + " ####################");
403: mod_jk.println();
404: // Dynamic /servet pages go to Tomcat
405:
406: generateStaticMappings(context, mod_jk);
407:
408: // InvokerInterceptor - it doesn't have a container,
409: // but it's implemented using a special module.
410:
411: // XXX we need to better collect all mappings
412:
413: if (context.getLoginConfig() != null) {
414: String loginPage = context.getLoginConfig().getLoginPage();
415: if (loginPage != null) {
416: int lpos = loginPage.lastIndexOf("/");
417: String jscurl = loginPage.substring(0, lpos + 1)
418: + "j_security_check";
419: addMapping(ctxPath, jscurl, mod_jk);
420: }
421: }
422: String[] servletMaps = context.findServletMappings();
423: for (int ii = 0; ii < servletMaps.length; ii++) {
424: addMapping(ctxPath, servletMaps[ii], mod_jk);
425: }
426: }
427:
428: /** Add an Apache extension mapping.
429: */
430: protected boolean addExtensionMapping(String ctxPath, String ext,
431: PrintWriter mod_jk) {
432: if (debug > 0)
433: log("Adding extension map for " + ctxPath + "/*." + ext);
434: mod_jk.println(indent + "JkMount " + ctxPath + "/*." + ext
435: + " " + jkWorker);
436: return true;
437: }
438:
439: /** Add a fulling specified Appache mapping.
440: */
441: protected boolean addMapping(String fullPath, PrintWriter mod_jk) {
442: if (debug > 0)
443: log("Adding map for " + fullPath);
444: mod_jk
445: .println(indent + "JkMount " + fullPath + " "
446: + jkWorker);
447: return true;
448: }
449:
450: /** Add a partially specified Appache mapping.
451: */
452: protected boolean addMapping(String ctxP, String ext,
453: PrintWriter mod_jk) {
454: if (debug > 0)
455: log("Adding map for " + ext);
456: if (!ext.startsWith("/"))
457: ext = "/" + ext;
458: if (ext.length() > 1)
459: mod_jk.println(indent + "JkMount " + ctxP + ext + " "
460: + jkWorker);
461: return true;
462: }
463:
464: private void generateWelcomeFiles(Context context,
465: PrintWriter mod_jk) {
466: String wf[] = context.findWelcomeFiles();
467: if (wf == null || wf.length == 0)
468: return;
469: mod_jk.print(indent + " DirectoryIndex ");
470: for (int i = 0; i < wf.length; i++) {
471: mod_jk.print(wf[i] + " ");
472: }
473: mod_jk.println();
474: }
475:
476: /** Mappings for static content. XXX need to add welcome files,
477: * mime mappings ( all will be handled by Mime and Static modules of
478: * apache ).
479: */
480: private void generateStaticMappings(Context context,
481: 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 + " \""
491: + docBase + "\"");
492: mod_jk.println();
493: } else {
494: if (getHost(context) != null) {
495: mod_jk.println(indent + "DocumentRoot \""
496: + getApacheDocBase(context) + "\"");
497: } else {
498: // For root context, ask user to update DocumentRoot setting.
499: // Using "Alias / " interferes with the Alias for other contexts.
500: mod_jk.println(indent
501: + "# Be sure to update DocumentRoot");
502: mod_jk.println(indent + "# to point to: \"" + docBase
503: + "\"");
504: }
505: }
506: mod_jk.println(indent + "<Directory \"" + docBase + "\">");
507: mod_jk.println(indent + " Options Indexes FollowSymLinks");
508:
509: generateWelcomeFiles(context, mod_jk);
510:
511: // XXX XXX Here goes the Mime types and welcome files !!!!!!!!
512: mod_jk.println(indent + "</Directory>");
513: mod_jk.println();
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
521: + "/WEB-INF/*\">");
522: mod_jk.println(indent + " AllowOverride None");
523: mod_jk.println(indent + " deny from all");
524: mod_jk.println(indent + "</Location>");
525: // Deny serving any files from META-INF
526: mod_jk.println();
527: mod_jk.println(indent + "<Location \"" + ctxPath
528: + "/META-INF/*\">");
529: mod_jk.println(indent + " AllowOverride None");
530: mod_jk.println(indent + " deny from all");
531: mod_jk.println(indent + "</Location>");
532: if (File.separatorChar == '\\') {
533: mod_jk.println(indent + "#");
534: mod_jk
535: .println(indent
536: + "# Use Directory too. On Windows, Location doesn't"
537: + " work unless case matches");
538: mod_jk.println(indent + "#");
539: mod_jk.println(indent + "<Directory \"" + docBase
540: + "/WEB-INF/\">");
541: mod_jk.println(indent + " AllowOverride None");
542: mod_jk.println(indent + " deny from all");
543: mod_jk.println(indent + "</Directory>");
544: mod_jk.println();
545: mod_jk.println(indent + "<Directory \"" + docBase
546: + "/META-INF/\">");
547: mod_jk.println(indent + " AllowOverride None");
548: mod_jk.println(indent + " deny from all");
549: mod_jk.println(indent + "</Directory>");
550: }
551: mod_jk.println();
552: }
553:
554: // -------------------- Utils --------------------
555:
556: private String getApacheDocBase(Context context) {
557: // Calculate the absolute path of the document base
558: String docBase = getAbsoluteDocBase(context);
559: if (File.separatorChar == '\\') {
560: // use separator preferred by Apache
561: docBase = docBase.replace('\\', '/');
562: }
563: return docBase;
564: }
565:
566: private String getVirtualHostAddress(String vhost, String vhostip) {
567: if (vhostip == null) {
568: if (vhost != null && vhost.length() > 0
569: && Character.isDigit(vhost.charAt(0)))
570: vhostip = vhost;
571: else
572: vhostip = "*";
573: }
574: return vhostip;
575: }
576:
577: }
|