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

   1: /* GnuConfiguration.java -- GNU Classpath implementation of JAAS Configuration
   2:    Copyright (C) 2006, 2010  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 = gnu.java.security.Configuration.DEBUG ?
 132:                 Logger.getLogger(GnuConfiguration.class.getName()) : null;
 133: 
 134:   /**
 135:    * The internal map of login modules keyed by application name. Each entry in
 136:    * this map is a {@link List} of {@link AppConfigurationEntry}s for that
 137:    * application name.
 138:    */
 139:   private Map loginModulesMap;
 140:   /** Our reference to our default syntax parser. */
 141:   private ConfigFileParser cp;
 142: 
 143:   // Constructor(s)
 144:   // --------------------------------------------------------------------------
 145: 
 146:   /** Trivial 0-arguments Constructor. */
 147:   public GnuConfiguration()
 148:   {
 149:     super();
 150: 
 151:     loginModulesMap = new HashMap();
 152:     cp = new ConfigFileParser();
 153:     init();
 154:   }
 155: 
 156:   // Class methods
 157:   // --------------------------------------------------------------------------
 158: 
 159:   // Instance methods
 160:   // --------------------------------------------------------------------------
 161: 
 162:   // Configuration abstract methods implementation ----------------------------
 163: 
 164:   /* (non-Javadoc)
 165:    * @see javax.security.auth.login.Configuration#getAppConfigurationEntry(java.lang.String)
 166:    */
 167:   public AppConfigurationEntry[] getAppConfigurationEntry(String appName)
 168:   {
 169:     if (appName == null)
 170:       return null;
 171: 
 172:     appName = appName.trim();
 173:     if (appName.length() == 0)
 174:       return null;
 175: 
 176:     List loginModules = (List) loginModulesMap.get(appName);
 177:     if (loginModules == null || loginModules.size() == 0)
 178:       return null;
 179: 
 180:     if (gnu.java.security.Configuration.DEBUG)
 181:       log.fine(appName + " -> " + loginModules.size() + " entry(ies)");
 182:     return (AppConfigurationEntry[]) loginModules.toArray(new AppConfigurationEntry[0]);
 183:   }
 184: 
 185:   /**
 186:    * Refreshes and reloads this <code>Configuration</code>.
 187:    *
 188:    * <p>This method causes this <code>Configuration</code> object to refresh /
 189:    * reload its contents following the locations and logic described above in
 190:    * the class documentation section.</p>
 191:    *
 192:    * @throws SecurityException if the caller does not have an
 193:    * {@link AuthPermission} for the action named
 194:    * <code>refreshLoginConfiguration</code>.
 195:    * @see AuthPermission
 196:    */
 197:   public void refresh()
 198:   {
 199:     SecurityManager sm = System.getSecurityManager();
 200:     if (sm != null)
 201:       sm.checkPermission(new AuthPermission("refreshLoginConfiguration"));
 202: 
 203:     loginModulesMap.clear();
 204:     init();
 205:   }
 206: 
 207:   // helper methods -----------------------------------------------------------
 208: 
 209:   /**
 210:    * Attempts to find and parse JAAS Login Configuration file(s) written in
 211:    * the default syntax. The locations searched are as descibed in the class
 212:    * documentation.
 213:    */
 214:   private void init()
 215:   {
 216:     if (processSecurityProperties())
 217:       {
 218:         if (gnu.java.security.Configuration.DEBUG)
 219:           log.fine("Using login configuration defined by Security property(ies)");
 220:       }
 221:     else if (processSystemProperty())
 222:       {
 223:         if (gnu.java.security.Configuration.DEBUG)
 224:           log.fine("Using login configuration defined by System property");
 225:       }
 226:     else if (processUserHome())
 227:       {
 228:         if (gnu.java.security.Configuration.DEBUG)
 229:           log.fine("Using login configuration defined in ${user.home}");
 230:       }
 231:     else
 232:       {
 233:         if (gnu.java.security.Configuration.DEBUG)
 234:           log.fine("No login configuration file found");
 235:       }
 236:   }
 237: 
 238:   /**
 239:    * Attempts to locate and parse one or more JAAS Login Configuration files
 240:    * defined as the values of the Security properties
 241:    * <i>java.security.auth.login.config.url.N</i>.
 242:    *
 243:    * @return <code>true</code> if it succeeds, and <code>false</code>
 244:    *         otherwsie.
 245:    */
 246:   private boolean processSecurityProperties()
 247:   {
 248:     boolean result = false;
 249:     int counter = 0;
 250:     String s;
 251:     while (true)
 252:       try
 253:         {
 254:           counter++;
 255:           s = Security.getProperty("java.security.auth.login.config.url."
 256:                                    + counter);
 257:           if (s == null)
 258:             break;
 259: 
 260:           s = s.trim();
 261:           if (s.length() != 0)
 262:             {
 263:               if (gnu.java.security.Configuration.DEBUG)
 264:                 log.fine("java.security.auth.login.config.url." + counter
 265:                          + " = " + s);
 266:               parseConfig(getInputStreamFromURL(s));
 267:               result = true;
 268:             }
 269:         }
 270:       catch (Throwable t)
 271:         {
 272:           if (gnu.java.security.Configuration.DEBUG)
 273:             log.fine("Exception while handling Security property at #"
 274:                      + counter + ". Continue: " + t);
 275:         }
 276:     return result;
 277:   }
 278: 
 279:   /**
 280:    * Attempts to open a designated string as a well-formed {@link URL}. If a
 281:    * {@link MalformedURLException} occurs, this method then tries to open that
 282:    * string as a {@link File} (with the same name). If it succeeds, an
 283:    * {@link InputStream} is constructed and returned.
 284:    *
 285:    * @param s
 286:    *          the designated name of either a {@link URL} or a {@link File}
 287:    *          assumed to contain a JAAS Login Configuration in the default
 288:    *          syntax.
 289:    * @return an {@link InputStream} around the data source.
 290:    * @throws IOException
 291:    *           if an exception occurs during the operation.
 292:    */
 293:   private InputStream getInputStreamFromURL(String s) throws IOException
 294:   {
 295:     InputStream result = null;
 296:     try
 297:       {
 298:         URL url = new URL(s);
 299:         result = url.openStream();
 300:       }
 301:     catch (MalformedURLException x)
 302:       {
 303:         if (gnu.java.security.Configuration.DEBUG)
 304:           log.fine("Failed opening as URL: " + s + ". Will try as File");
 305:         result = new FileInputStream(s);
 306:       }
 307:     return result;
 308:   }
 309: 
 310:   /**
 311:    * Attempts to locate and parse a JAAS Login Configuration file defined as the
 312:    * value of the System property <i>java.security.auth.login.config</i>.
 313:    *
 314:    * @return <code>true</code> if it succeeds, and <code>false</code>
 315:    *         otherwsie.
 316:    */
 317:   private boolean processSystemProperty()
 318:   {
 319:     boolean result = false;
 320:     try
 321:       {
 322:         String s = System.getProperty("java.security.auth.login.config");
 323:         if (s != null)
 324:           {
 325:             s = s.trim();
 326:             if (s.length() != 0)
 327:               {
 328:                 if (gnu.java.security.Configuration.DEBUG)
 329:                   log.fine("java.security.auth.login.config = " + s);
 330:                 parseConfig(getInputStreamFromURL(s));
 331:                 result = true;
 332:               }
 333:           }
 334:       }
 335:     catch (Throwable t)
 336:       {
 337:         if (gnu.java.security.Configuration.DEBUG)
 338:           log.fine("Exception while handling System property. Continue: " + t);
 339:       }
 340:     return result;
 341:   }
 342: 
 343:   /**
 344:    * Attempts to locate and parse a JAAS Login Configuration file named either
 345:    * as <i>.java.login.config</i> or <i>java.login.config</i> (without the
 346:    * leading dot) in the folder referenced by the System property
 347:    * <code>user.home</code>.
 348:    *
 349:    * @return <code>true</code> if it succeeds, and <code>false</code>
 350:    *         otherwsie.
 351:    */
 352:   private boolean processUserHome()
 353:   {
 354:     boolean result = false;
 355:     try
 356:       {
 357:         File userHome = getUserHome();
 358:         if (userHome == null)
 359:           return result;
 360: 
 361:         File jaasFile;
 362:         jaasFile = getConfigFromUserHome(userHome, ".java.login.config");
 363:         if (jaasFile == null)
 364:           jaasFile = getConfigFromUserHome(userHome, "java.login.config");
 365: 
 366:         if (jaasFile == null)
 367:           {
 368:             if (gnu.java.security.Configuration.DEBUG)
 369:               log.fine("Login Configuration file, in " + userHome
 370:                        + ", does not exist or is inaccessible");
 371:             return result;
 372:           }
 373: 
 374:         FileInputStream fis = new FileInputStream(jaasFile);
 375:         parseConfig(fis);
 376:         result = true;
 377:       }
 378:     catch (Throwable t)
 379:       {
 380:         if (gnu.java.security.Configuration.DEBUG)
 381:           log.fine("Exception (ignored) while handling ${user.home}: " + t);
 382:       }
 383:     return result;
 384:   }
 385: 
 386:   private void parseConfig(InputStream configStream) throws IOException
 387:   {
 388:     cp.parse(new InputStreamReader(configStream, "UTF-8"));
 389:     Map loginModulesMap = cp.getLoginModulesMap();
 390:     mergeLoginModules(loginModulesMap);
 391:   }
 392: 
 393:   private void mergeLoginModules(Map otherLoginModules)
 394:   {
 395:     if (otherLoginModules == null || otherLoginModules.size() < 1)
 396:       return;
 397: 
 398:     for (Iterator it = otherLoginModules.keySet().iterator(); it.hasNext();)
 399:       {
 400:         String appName = (String) it.next();
 401:         List thatListOfACEs = (List) otherLoginModules.get(appName);
 402:         if (thatListOfACEs == null || thatListOfACEs.size() < 1)
 403:           continue;
 404: 
 405:         List thisListsOfACEs = (List) loginModulesMap.get(appName);
 406:         if (thisListsOfACEs == null)
 407:           loginModulesMap.put(appName, thatListOfACEs);
 408:         else
 409:           thisListsOfACEs.addAll(thatListOfACEs);
 410:       }
 411:   }
 412: 
 413:   private File getUserHome()
 414:   {
 415:     String uh = System.getProperty("user.home");
 416:     if (uh == null || uh.trim().length() == 0)
 417:       {
 418:         if (gnu.java.security.Configuration.DEBUG)
 419:           log.fine("User home path is not set or is empty");
 420:         return null;
 421:       }
 422:     uh = uh.trim();
 423:     File result = new File(uh);
 424:     if (! result.exists())
 425:       {
 426:         if (gnu.java.security.Configuration.DEBUG)
 427:           log.fine("User home '" + uh + "' does not exist");
 428:         return null;
 429:       }
 430:     if (! result.isDirectory())
 431:       {
 432:         if (gnu.java.security.Configuration.DEBUG)
 433:           log.fine("User home '" + uh + "' is not a directory");
 434:         return null;
 435:       }
 436:     if (! result.canRead())
 437:       {
 438:         if (gnu.java.security.Configuration.DEBUG)
 439:           log.fine("User home '" + uh + "' is not readable");
 440:         return null;
 441:       }
 442:     return result;
 443:   }
 444: 
 445:   private File getConfigFromUserHome(File userHome, String fileName)
 446:   {
 447:     File result = new File(userHome, fileName);
 448:     if (! result.exists())
 449:       {
 450:         if (gnu.java.security.Configuration.DEBUG)
 451:           log.fine("File '" + fileName + "' does not exist in user's home");
 452:         return null;
 453:       }
 454:     if (! result.isFile())
 455:       {
 456:         if (gnu.java.security.Configuration.DEBUG)
 457:           log.fine("File '" + fileName + "' in user's home is not a file");
 458:         return null;
 459:       }
 460:     if (! result.canRead())
 461:       {
 462:         if (gnu.java.security.Configuration.DEBUG)
 463:           log.fine("File '" + fileName + "' in user's home is not readable");
 464:         return null;
 465:       }
 466:     return result;
 467:   }
 468: }