Source for gnu.javax.security.auth.login.GnuConfiguration

   1: /* GnuConfiguration.java -- GNU Classpath implementation of JAAS Configuration
   2:    Copyright (C) 2006 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package gnu.javax.security.auth.login;
  40: 
  41: import java.io.File;
  42: import java.io.FileInputStream;
  43: import java.io.IOException;
  44: import java.io.InputStream;
  45: import java.io.InputStreamReader;
  46: import java.net.MalformedURLException;
  47: import java.net.URL;
  48: import java.security.Security;
  49: import java.util.HashMap;
  50: import java.util.Iterator;
  51: import java.util.List;
  52: import java.util.Map;
  53: import java.util.logging.Logger;
  54: 
  55: import javax.security.auth.AuthPermission;
  56: import javax.security.auth.login.AppConfigurationEntry;
  57: import javax.security.auth.login.Configuration;
  58: 
  59: /**
  60:  * An implementation of the {@link Configuration} class which interprets JAAS
  61:  * Login Configuration files written in the <i>default</i> syntax described in
  62:  * the publicly available documentation of that class. A more formal definition
  63:  * of this syntax is as follows:
  64:  * 
  65:  * <pre>
  66:  *   CONFIG              ::= APP_OR_OTHER_ENTRY+
  67:  *   APP_OR_OTHER_ENTRY  ::= APP_NAME_OR_OTHER JAAS_CONFIG_BLOCK
  68:  *   APP_NAME_OR_OTHER   ::= APP_NAME
  69:  *                         | 'other'
  70:  *   JAAS_CONFIG_BLOCK   ::= '{' (LOGIN_MODULE_ENTRY ';')+ '}' ';'
  71:  *   LOGIN_MODULE_ENTRY  ::= MODULE_CLASS FLAG MODULE_OPTION* ';'
  72:  *   FLAG                ::= 'required'
  73:  *                         | 'requisite'
  74:  *                         | 'sufficient'
  75:  *                         | 'optional'
  76:  *   MODULE_OPTION       ::= PARAM_NAME '=' PARAM_VALUE
  77:  *
  78:  *   APP_NAME     ::= JAVA_IDENTIFIER
  79:  *   MODULE_CLASS ::= JAVA_IDENTIFIER ('.' JAVA_IDENTIFIER)*
  80:  *   PARAM_NAME   ::= STRING
  81:  *   PARAM_VALUE  ::= '"' STRING '"' | ''' STRING ''' | STRING
  82:  * </pre>
  83:  * 
  84:  * <p>This implementation will specifically attempt to process one or more
  85:  * Login Configuration files in the following locations, and when found parse
  86:  * them and merge their contents. The locations, and the order in which they are
  87:  * investigated, follows:</p>
  88:  * 
  89:  * <ol>
  90:  *   <li>If the following Security properties:
  91:  *   <i>java.security.auth.login.config.url.<b>N</b></i>, where <i><b>N</b></i>
  92:  *   is a digit, from <code>1</code> to an arbitrary number, are defined, then
  93:  *   the value of each of those properties will be considered as a JAAS Login
  94:  *   Configuration file written in the default syntax. This implementation will
  95:  *   attempt parsing all such files.
  96:  *
  97:  *   <p>It is worth noting the following:
  98:  *     <ul>
  99:  *       <li>The GNU Classpath security file, named <i>classpath.security</i>,
 100:  *       where all Security properties are encoded, is usually located in
 101:  *       <i>/usr/local/classpath/lib/security</i> folder.</li>
 102:  *       
 103:  *       <li>The numbers used in the properties
 104:  *       <i>java.security.auth.login.config.url.<b>N</b></i> MUST be sequential,
 105:  *       with no breaks in-between.</li>
 106:  *     </ul>
 107:  *   </p>
 108:  *
 109:  *   <p>If at least one of the designated Configuration files was found, and
 110:  *   was parsed correctly, then no other location will be inspected.</p></li>
 111:  *
 112:  *   <li>If the System property named <i>java.security.auth.login.config</i>
 113:  *   is not null or empty, its contents are then interpreted as a URL to a
 114:  *   JAAS Login Configuration file written in the default syntax.
 115:  *
 116:  *   <p>If this System property is defined, and the file it refers to was
 117:  *   parsed correctly, then no other location will be inspected.</p></li>
 118:  *
 119:  *   <li>If a file named <i>.java.login.config</i> or <i>java.login.config</i>
 120:  *   (in that order) is found in the location referenced by the value of the
 121:  *   System property <i>user.home</i>, then that file is parsed as a JAAS Login
 122:  *   Configuration written in the default syntax.</li>
 123:  *   
 124:  *   <li>If none of the above resulted in a correctly parsed JAAS Login
 125:  *   Configuration file, then this implementation will install a <i>Null
 126:  *   Configuration</i> which basically does not recognize any Application.</li>
 127:  * </ol>
 128:  */
 129: public final class GnuConfiguration extends Configuration
 130: {
 131:   private static final Logger log = Logger.getLogger(GnuConfiguration.class.getName());
 132:   /**
 133:    * The internal map of login modules keyed by application name. Each entry in
 134:    * this map is a {@link List} of {@link AppConfigurationEntry}s for that
 135:    * application name.
 136:    */
 137:   private Map loginModulesMap;
 138:   /** Our reference to our default syntax parser. */
 139:   private ConfigFileParser cp;
 140: 
 141:   // Constructor(s)
 142:   // --------------------------------------------------------------------------
 143: 
 144:   /** Trivial 0-arguments Constructor. */
 145:   public GnuConfiguration()
 146:   {
 147:     super();
 148: 
 149:     loginModulesMap = new HashMap();
 150:     cp = new ConfigFileParser();
 151:     init();
 152:   }
 153: 
 154:   // Class methods
 155:   // --------------------------------------------------------------------------
 156: 
 157:   // Instance methods
 158:   // --------------------------------------------------------------------------
 159: 
 160:   // Configuration abstract methods implementation ----------------------------
 161: 
 162:   /* (non-Javadoc)
 163:    * @see javax.security.auth.login.Configuration#getAppConfigurationEntry(java.lang.String)
 164:    */
 165:   public AppConfigurationEntry[] getAppConfigurationEntry(String appName)
 166:   {
 167:     if (appName == null)
 168:       return null;
 169: 
 170:     appName = appName.trim();
 171:     if (appName.length() == 0)
 172:       return null;
 173: 
 174:     List loginModules = (List) loginModulesMap.get(appName);
 175:     if (loginModules == null || loginModules.size() == 0)
 176:       return null;
 177: 
 178:     if (gnu.java.security.Configuration.DEBUG)
 179:       log.fine(appName + " -> " + loginModules.size() + " entry(ies)");
 180:     return (AppConfigurationEntry[]) loginModules.toArray(new AppConfigurationEntry[0]);
 181:   }
 182: 
 183:   /**
 184:    * Refreshes and reloads this <code>Configuration</code>.
 185:    * 
 186:    * <p>This method causes this <code>Configuration</code> object to refresh /
 187:    * reload its contents following the locations and logic described above in
 188:    * the class documentation section.</p>
 189:    * 
 190:    * @throws SecurityException if the caller does not have an
 191:    * {@link AuthPermission} for the action named
 192:    * <code>refreshLoginConfiguration</code>.
 193:    * @see AuthPermission
 194:    */
 195:   public void refresh()
 196:   {
 197:     SecurityManager sm = System.getSecurityManager();
 198:     if (sm != null)
 199:       sm.checkPermission(new AuthPermission("refreshLoginConfiguration"));
 200: 
 201:     loginModulesMap.clear();
 202:     init();
 203:   }
 204: 
 205:   // helper methods -----------------------------------------------------------
 206: 
 207:   /**
 208:    * Attempts to find and parse JAAS Login Configuration file(s) written in
 209:    * the default syntax. The locations searched are as descibed in the class
 210:    * documentation.
 211:    */
 212:   private void init()
 213:   {
 214:     if (processSecurityProperties())
 215:       {
 216:         if (gnu.java.security.Configuration.DEBUG)
 217:           log.fine("Using login configuration defined by Security property(ies)");
 218:       }
 219:     else if (processSystemProperty())
 220:       {
 221:         if (gnu.java.security.Configuration.DEBUG)
 222:           log.fine("Using login configuration defined by System property");
 223:       }
 224:     else if (processUserHome())
 225:       {
 226:         if (gnu.java.security.Configuration.DEBUG)
 227:           log.fine("Using login configuration defined in ${user.home}");
 228:       }
 229:     else
 230:       {
 231:         if (gnu.java.security.Configuration.DEBUG)
 232:           log.fine("No login configuration file found");
 233:       }
 234:   }
 235: 
 236:   /**
 237:    * Attempts to locate and parse one or more JAAS Login Configuration files
 238:    * defined as the values of the Security properties
 239:    * <i>java.security.auth.login.config.url.N</i>.
 240:    * 
 241:    * @return <code>true</code> if it succeeds, and <code>false</code>
 242:    *         otherwsie.
 243:    */
 244:   private boolean processSecurityProperties()
 245:   {
 246:     boolean result = false;
 247:     int counter = 0;
 248:     String s;
 249:     while (true)
 250:       try
 251:         {
 252:           counter++;
 253:           s = Security.getProperty("java.security.auth.login.config.url."
 254:                                    + counter);
 255:           if (s == null)
 256:             break;
 257: 
 258:           s = s.trim();
 259:           if (s.length() != 0)
 260:             {
 261:               if (gnu.java.security.Configuration.DEBUG)
 262:                 log.fine("java.security.auth.login.config.url." + counter
 263:                          + " = " + s);
 264:               parseConfig(getInputStreamFromURL(s));
 265:               result = true;
 266:             }
 267:         }
 268:       catch (Throwable t)
 269:         {
 270:           if (gnu.java.security.Configuration.DEBUG)
 271:             log.fine("Exception while handling Security property at #"
 272:                      + counter + ". Continue: " + t);
 273:         }
 274:     return result;
 275:   }
 276: 
 277:   /**
 278:    * Attempts to open a designated string as a well-formed {@link URL}. If a
 279:    * {@link MalformedURLException} occurs, this method then tries to open that
 280:    * string as a {@link File} (with the same name). If it succeeds, an
 281:    * {@link InputStream} is constructed and returned.
 282:    * 
 283:    * @param s
 284:    *          the designated name of either a {@link URL} or a {@link File}
 285:    *          assumed to contain a JAAS Login Configuration in the default
 286:    *          syntax.
 287:    * @return an {@link InputStream} around the data source.
 288:    * @throws IOException
 289:    *           if an exception occurs during the operation.
 290:    */
 291:   private InputStream getInputStreamFromURL(String s) throws IOException
 292:   {
 293:     InputStream result = null;
 294:     try
 295:       {
 296:         URL url = new URL(s);
 297:         result = url.openStream();
 298:       }
 299:     catch (MalformedURLException x)
 300:       {
 301:         if (gnu.java.security.Configuration.DEBUG)
 302:           log.fine("Failed opening as URL: " + s + ". Will try as File");
 303:         result = new FileInputStream(s);
 304:       }
 305:     return result;
 306:   }
 307: 
 308:   /**
 309:    * Attempts to locate and parse a JAAS Login Configuration file defined as the
 310:    * value of the System property <i>java.security.auth.login.config</i>.
 311:    * 
 312:    * @return <code>true</code> if it succeeds, and <code>false</code>
 313:    *         otherwsie.
 314:    */
 315:   private boolean processSystemProperty()
 316:   {
 317:     boolean result = false;
 318:     try
 319:       {
 320:         String s = System.getProperty("java.security.auth.login.config");
 321:         if (s != null)
 322:           {
 323:             s = s.trim();
 324:             if (s.length() != 0)
 325:               {
 326:                 if (gnu.java.security.Configuration.DEBUG)
 327:                   log.fine("java.security.auth.login.config = " + s);
 328:                 parseConfig(getInputStreamFromURL(s));
 329:                 result = true;
 330:               }
 331:           }
 332:       }
 333:     catch (Throwable t)
 334:       {
 335:         if (gnu.java.security.Configuration.DEBUG)
 336:           log.fine("Exception while handling System property. Continue: " + t);
 337:       }
 338:     return result;
 339:   }
 340: 
 341:   /**
 342:    * Attempts to locate and parse a JAAS Login Configuration file named either
 343:    * as <i>.java.login.config</i> or <i>java.login.config</i> (without the
 344:    * leading dot) in the folder referenced by the System property
 345:    * <code>user.home</code>.
 346:    * 
 347:    * @return <code>true</code> if it succeeds, and <code>false</code>
 348:    *         otherwsie.
 349:    */
 350:   private boolean processUserHome()
 351:   {
 352:     boolean result = false;
 353:     try
 354:       {
 355:         File userHome = getUserHome();
 356:         if (userHome == null)
 357:           return result;
 358: 
 359:         File jaasFile;
 360:         jaasFile = getConfigFromUserHome(userHome, ".java.login.config");
 361:         if (jaasFile == null)
 362:           jaasFile = getConfigFromUserHome(userHome, "java.login.config");
 363: 
 364:         if (jaasFile == null)
 365:           {
 366:             if (gnu.java.security.Configuration.DEBUG)
 367:               log.fine("Login Configuration file, in " + userHome
 368:                        + ", does not exist or is inaccessible");
 369:             return result;
 370:           }
 371: 
 372:         FileInputStream fis = new FileInputStream(jaasFile);
 373:         parseConfig(fis);
 374:         result = true;
 375:       }
 376:     catch (Throwable t)
 377:       {
 378:         if (gnu.java.security.Configuration.DEBUG)
 379:           log.fine("Exception (ignored) while handling ${user.home}: " + t);
 380:       }
 381:     return result;
 382:   }
 383: 
 384:   private void parseConfig(InputStream configStream) throws IOException
 385:   {
 386:     cp.parse(new InputStreamReader(configStream, "UTF-8"));
 387:     Map loginModulesMap = cp.getLoginModulesMap();
 388:     mergeLoginModules(loginModulesMap);
 389:   }
 390: 
 391:   private void mergeLoginModules(Map otherLoginModules)
 392:   {
 393:     if (otherLoginModules == null || otherLoginModules.size() < 1)
 394:       return;
 395: 
 396:     for (Iterator it = otherLoginModules.keySet().iterator(); it.hasNext();)
 397:       {
 398:         String appName = (String) it.next();
 399:         List thatListOfACEs = (List) otherLoginModules.get(appName);
 400:         if (thatListOfACEs == null || thatListOfACEs.size() < 1)
 401:           continue;
 402: 
 403:         List thisListsOfACEs = (List) loginModulesMap.get(appName);
 404:         if (thisListsOfACEs == null)
 405:           loginModulesMap.put(appName, thatListOfACEs);
 406:         else
 407:           thisListsOfACEs.addAll(thatListOfACEs);
 408:       }
 409:   }
 410: 
 411:   private File getUserHome()
 412:   {
 413:     String uh = System.getProperty("user.home");
 414:     if (uh == null || uh.trim().length() == 0)
 415:       {
 416:         if (gnu.java.security.Configuration.DEBUG)
 417:           log.fine("User home path is not set or is empty");
 418:         return null;
 419:       }
 420:     uh = uh.trim();
 421:     File result = new File(uh);
 422:     if (! result.exists())
 423:       {
 424:         if (gnu.java.security.Configuration.DEBUG)
 425:           log.fine("User home '" + uh + "' does not exist");
 426:         return null;
 427:       }
 428:     if (! result.isDirectory())
 429:       {
 430:         if (gnu.java.security.Configuration.DEBUG)
 431:           log.fine("User home '" + uh + "' is not a directory");
 432:         return null;
 433:       }
 434:     if (! result.canRead())
 435:       {
 436:         if (gnu.java.security.Configuration.DEBUG)
 437:           log.fine("User home '" + uh + "' is not readable");
 438:         return null;
 439:       }
 440:     return result;
 441:   }
 442: 
 443:   private File getConfigFromUserHome(File userHome, String fileName)
 444:   {
 445:     File result = new File(userHome, fileName);
 446:     if (! result.exists())
 447:       {
 448:         if (gnu.java.security.Configuration.DEBUG)
 449:           log.fine("File '" + fileName + "' does not exist in user's home");
 450:         return null;
 451:       }
 452:     if (! result.isFile())
 453:       {
 454:         if (gnu.java.security.Configuration.DEBUG)
 455:           log.fine("File '" + fileName + "' in user's home is not a file");
 456:         return null;
 457:       }
 458:     if (! result.canRead())
 459:       {
 460:         if (gnu.java.security.Configuration.DEBUG)
 461:           log.fine("File '" + fileName + "' in user's home is not readable");
 462:         return null;
 463:       }
 464:     return result;
 465:   }
 466: }