001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.jk.config;
019:
020: import java.io.File;
021: import java.io.FileWriter;
022: import java.io.IOException;
023: import java.io.PrintWriter;
024: import java.util.Date;
025: import java.util.Hashtable;
026:
027: import org.apache.catalina.Context;
028: import org.apache.catalina.Host;
029:
030: /* The idea is to keep all configuration in server.xml and
031: the normal apache config files. We don't want people to
032: touch apache ( or IIS, NES ) config files unless they
033: want to and know what they're doing ( better than we do :-).
034:
035: One nice feature ( if someone sends it ) would be to
036: also edit httpd.conf to add the include.
037:
038: We'll generate a number of configuration files - this one
039: is trying to generate a native apache config file.
040:
041: Some web.xml mappings do not "map" to server configuration - in
042: this case we need to fallback to forward all requests to tomcat.
043:
044: Ajp14 will add to that the posibility to have tomcat and
045: apache on different machines, and many other improvements -
046: but this should also work for Ajp12, Ajp13 and Jni.
047:
048: */
049:
050: /**
051: Generates automatic apache mod_jk configurations based on
052: the Tomcat server.xml settings and the war contexts
053: initialized during startup.
054: <p>
055: This config interceptor is enabled by inserting an ApacheConfig
056: <code>Listener</code> in
057: the server.xml file like so:
058: <pre>
059: * < Server ... >
060: * ...
061: * <Listener className=<b>org.apache.ajp.tomcat4.config.ApacheConfig</b>
062: * <i>options</i> />
063: * ...
064: * < /Server >
065: </pre>
066: where <i>options</i> can include any of the following attributes:
067: <ul>
068: <li><b>configHome</b> - default parent directory for the following paths.
069: If not set, this defaults to TOMCAT_HOME. Ignored
070: whenever any of the following paths is absolute.
071: </li>
072: <li><b>jkConfig</b> - path to use for writing Apache mod_jk conf file. If
073: not set, defaults to
074: "conf/auto/mod_jk.conf".</li>
075: <li><b>workersConfig</b> - path to workers.properties file used by
076: mod_jk. If not set, defaults to
077: "conf/jk/workers.properties".</li>
078: <li><b>modJk</b> - path to Apache mod_jk plugin file. If not set,
079: defaults to "modules/mod_jk.dll" on windows,
080: "modules/mod_jk.nlm" on netware, and
081: "libexec/mod_jk.so" everywhere else.</li>
082: <li><b>jkLog</b> - path to log file to be used by mod_jk.</li>
083: <li><b>jkDebug</b> - JK Loglevel setting. May be debug, info, error, or emerg.
084: If not set, defaults to emerg.</li>
085: <li><b>jkWorker</b> The desired worker. Must be set to one of the workers
086: defined in the workers.properties file. "ajp12", "ajp13"
087: or "inprocess" are the workers found in the default
088: workers.properties file. If not specified, defaults
089: to "ajp13" if an Ajp13Interceptor is in use, otherwise
090: it defaults to "ajp12".</li>
091: <li><b>forwardAll</b> - If true, forward all requests to Tomcat. This helps
092: insure that all the behavior configured in the web.xml
093: file functions correctly. If false, let Apache serve
094: static resources. The default is true.
095: Warning: When false, some configuration in
096: the web.xml may not be duplicated in Apache.
097: Review the mod_jk conf file to see what
098: configuration is actually being set in Apache.</li>
099: <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 = org.apache.juli.logging.LogFactory
121: .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:
240: // -------------------- Generate config --------------------
241:
242: protected PrintWriter getWriter() throws IOException {
243: String abJkConfig = jkConfig.getAbsolutePath();
244: return new PrintWriter(new FileWriter(abJkConfig, append));
245: }
246:
247: // -------------------- Config sections --------------------
248:
249: /** Generate the loadModule and general options
250: */
251: protected boolean generateJkHead(PrintWriter mod_jk) {
252:
253: mod_jk.println("########## Auto generated on " + new Date()
254: + "##########");
255: mod_jk.println();
256:
257: // Fail if mod_jk not found, let the user know the problem
258: // instead of running into problems later.
259: if (!modJk.exists()) {
260: log.info("mod_jk location: " + modJk);
261: log.info("Make sure it is installed corectly or "
262: + " set the config location");
263: log.info("Using <Listener className=\""
264: + getClass().getName()
265: + "\" modJk=\"PATH_TO_MOD_JK.SO_OR_DLL\" />");
266: }
267:
268: // Verify the file exists !!
269: mod_jk.println("<IfModule !mod_jk.c>");
270: mod_jk.println(" LoadModule jk_module \""
271: + modJk.toString().replace('\\', '/') + "\"");
272: mod_jk.println("</IfModule>");
273: mod_jk.println();
274:
275: // Fail if workers file not found, let the user know the problem
276: // instead of running into problems later.
277: if (!workersConfig.exists()) {
278: log.warn("Can't find workers.properties at "
279: + workersConfig);
280: log.warn("Please install it in the default location or "
281: + " set the config location");
282: log.warn("Using <Listener className=\""
283: + getClass().getName()
284: + "\" workersConfig=\"FULL_PATH\" />");
285: return false;
286: }
287:
288: mod_jk.println("JkWorkersFile \""
289: + workersConfig.toString().replace('\\', '/') + "\"");
290:
291: mod_jk.println("JkLogFile \""
292: + jkLog.toString().replace('\\', '/') + "\"");
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: String ctxPath = context.getPath();
358: if (ctxPath == null)
359: return;
360:
361: String nPath = ("".equals(ctxPath)) ? "/" : ctxPath;
362:
363: mod_jk.println();
364: mod_jk.println(indent + "JkMount " + nPath + " " + jkWorker);
365: if ("".equals(ctxPath)) {
366: mod_jk.println(indent + "JkMount " + nPath + "* "
367: + jkWorker);
368: if (context.getParent() instanceof Host) {
369: mod_jk.println(indent + "DocumentRoot \""
370: + getApacheDocBase(context) + "\"");
371: } else {
372: mod_jk
373: .println(indent
374: + "# To avoid Apache serving root welcome files from htdocs, update DocumentRoot");
375: mod_jk.println(indent + "# to point to: \""
376: + getApacheDocBase(context) + "\"");
377: }
378:
379: } else {
380: mod_jk.println(indent + "JkMount " + nPath + "/* "
381: + jkWorker);
382: }
383: }
384:
385: private void generateNameVirtualHost(PrintWriter mod_jk, String ip) {
386: if (!NamedVirtualHosts.containsKey(ip)) {
387: mod_jk.println("NameVirtualHost " + ip + "");
388: NamedVirtualHosts.put(ip, ip);
389: }
390: }
391:
392: // -------------------- Apache serves static mode --------------------
393: // This is not going to work for all apps. We fall back to stupid mode.
394:
395: protected void generateContextMappings(Context context,
396: PrintWriter mod_jk) {
397: String ctxPath = context.getPath();
398: Host vhost = getHost(context);
399:
400: if (noRoot && "".equals(ctxPath)) {
401: log
402: .debug("Ignoring root context in non-forward-all mode ");
403: return;
404: }
405:
406: mod_jk.println();
407: mod_jk.println(indent + "#################### "
408: + ((vhost != null) ? vhost.getName() + ":" : "")
409: + (("".equals(ctxPath)) ? "/" : ctxPath)
410: + " ####################");
411: mod_jk.println();
412: // Dynamic /servet pages go to Tomcat
413:
414: generateStaticMappings(context, mod_jk);
415:
416: // InvokerInterceptor - it doesn't have a container,
417: // but it's implemented using a special module.
418:
419: // XXX we need to better collect all mappings
420:
421: if (context.getLoginConfig() != null) {
422: String loginPage = context.getLoginConfig().getLoginPage();
423: if (loginPage != null) {
424: int lpos = loginPage.lastIndexOf("/");
425: String jscurl = loginPage.substring(0, lpos + 1)
426: + "j_security_check";
427: addMapping(ctxPath, jscurl, mod_jk);
428: }
429: }
430: String[] servletMaps = context.findServletMappings();
431: for (int ii = 0; ii < servletMaps.length; ii++) {
432: addMapping(ctxPath, servletMaps[ii], mod_jk);
433: }
434: }
435:
436: /** Add an Apache extension mapping.
437: */
438: protected boolean addExtensionMapping(String ctxPath, String ext,
439: PrintWriter mod_jk) {
440: if (log.isDebugEnabled())
441: log.debug("Adding extension map for " + ctxPath + "/*."
442: + ext);
443: mod_jk.println(indent + "JkMount " + ctxPath + "/*." + ext
444: + " " + jkWorker);
445: return true;
446: }
447:
448: /** Add a fulling specified Appache mapping.
449: */
450: protected boolean addMapping(String fullPath, PrintWriter mod_jk) {
451: if (log.isDebugEnabled())
452: log.debug("Adding map for " + fullPath);
453: mod_jk
454: .println(indent + "JkMount " + fullPath + " "
455: + jkWorker);
456: return true;
457: }
458:
459: /** Add a partially specified Appache mapping.
460: */
461: protected boolean addMapping(String ctxP, String ext,
462: PrintWriter mod_jk) {
463: if (log.isDebugEnabled())
464: log.debug("Adding map for " + ext);
465: if (!ext.startsWith("/"))
466: ext = "/" + ext;
467: if (ext.length() > 1)
468: mod_jk.println(indent + "JkMount " + ctxP + ext + " "
469: + jkWorker);
470: return true;
471: }
472:
473: private void generateWelcomeFiles(Context context,
474: PrintWriter mod_jk) {
475: String wf[] = context.findWelcomeFiles();
476: if (wf == null || wf.length == 0)
477: return;
478: mod_jk.print(indent + " DirectoryIndex ");
479: for (int i = 0; i < wf.length; i++) {
480: mod_jk.print(wf[i] + " ");
481: }
482: mod_jk.println();
483: }
484:
485: /** Mappings for static content. XXX need to add welcome files,
486: * mime mappings ( all will be handled by Mime and Static modules of
487: * apache ).
488: */
489: private void generateStaticMappings(Context context,
490: PrintWriter mod_jk) {
491: String ctxPath = context.getPath();
492:
493: // Calculate the absolute path of the document base
494: String docBase = getApacheDocBase(context);
495:
496: if (!"".equals(ctxPath)) {
497: // Static files will be served by Apache
498: mod_jk.println(indent + "# Static files ");
499: mod_jk.println(indent + "Alias " + ctxPath + " \""
500: + docBase + "\"");
501: mod_jk.println();
502: } else {
503: if (getHost(context) != null) {
504: mod_jk.println(indent + "DocumentRoot \""
505: + getApacheDocBase(context) + "\"");
506: } else {
507: // For root context, ask user to update DocumentRoot setting.
508: // Using "Alias / " interferes with the Alias for other contexts.
509: mod_jk.println(indent
510: + "# Be sure to update DocumentRoot");
511: mod_jk.println(indent + "# to point to: \"" + docBase
512: + "\"");
513: }
514: }
515: mod_jk.println(indent + "<Directory \"" + docBase + "\">");
516: mod_jk.println(indent + " Options Indexes FollowSymLinks");
517:
518: generateWelcomeFiles(context, mod_jk);
519:
520: // XXX XXX Here goes the Mime types and welcome files !!!!!!!!
521: mod_jk.println(indent + "</Directory>");
522: mod_jk.println();
523:
524: // Deny serving any files from WEB-INF
525: mod_jk.println();
526: mod_jk.println(indent
527: + "# Deny direct access to WEB-INF and META-INF");
528: mod_jk.println(indent + "#");
529: mod_jk.println(indent + "<Location \"" + ctxPath
530: + "/WEB-INF/*\">");
531: mod_jk.println(indent + " AllowOverride None");
532: mod_jk.println(indent + " deny from all");
533: mod_jk.println(indent + "</Location>");
534: // Deny serving any files from META-INF
535: mod_jk.println();
536: mod_jk.println(indent + "<Location \"" + ctxPath
537: + "/META-INF/*\">");
538: mod_jk.println(indent + " AllowOverride None");
539: mod_jk.println(indent + " deny from all");
540: mod_jk.println(indent + "</Location>");
541: if (File.separatorChar == '\\') {
542: mod_jk.println(indent + "#");
543: mod_jk
544: .println(indent
545: + "# Use Directory too. On Windows, Location doesn't"
546: + " work unless case matches");
547: mod_jk.println(indent + "#");
548: mod_jk.println(indent + "<Directory \"" + docBase
549: + "/WEB-INF/\">");
550: mod_jk.println(indent + " AllowOverride None");
551: mod_jk.println(indent + " deny from all");
552: mod_jk.println(indent + "</Directory>");
553: mod_jk.println();
554: mod_jk.println(indent + "<Directory \"" + docBase
555: + "/META-INF/\">");
556: mod_jk.println(indent + " AllowOverride None");
557: mod_jk.println(indent + " deny from all");
558: mod_jk.println(indent + "</Directory>");
559: }
560: mod_jk.println();
561: }
562:
563: // -------------------- Utils --------------------
564:
565: private String getApacheDocBase(Context context) {
566: // Calculate the absolute path of the document base
567: String docBase = getAbsoluteDocBase(context);
568: if (File.separatorChar == '\\') {
569: // use separator preferred by Apache
570: docBase = docBase.replace('\\', '/');
571: }
572: return docBase;
573: }
574:
575: private String getVirtualHostAddress(String vhost, String vhostip) {
576: if (vhostip == null) {
577: if (vhost != null && vhost.length() > 0
578: && Character.isDigit(vhost.charAt(0)))
579: vhostip = vhost;
580: else
581: vhostip = "*";
582: }
583: return vhostip;
584: }
585:
586: }
|