001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.tools; 018 019import static com.forgerock.opendj.cli.CliMessages.INFO_FILE_PLACEHOLDER; 020import static com.forgerock.opendj.cli.CommonArguments.*; 021import static com.forgerock.opendj.cli.Utils.*; 022 023import static org.opends.messages.ToolMessages.*; 024import static org.opends.messages.ToolMessages.INFO_CONFIGFILE_PLACEHOLDER; 025import static org.opends.messages.ToolMessages.INFO_DESCRIPTION_CONFIG_FILE; 026import static org.opends.server.protocols.ldap.LDAPResultCode.*; 027import static org.opends.server.util.StaticUtils.*; 028 029import java.io.Console; 030import java.io.IOException; 031import java.io.OutputStream; 032import java.io.PrintStream; 033import java.util.ArrayList; 034import java.util.Collection; 035import java.util.Collections; 036 037import org.forgerock.i18n.LocalizableMessage; 038import org.forgerock.opendj.ldap.ByteString; 039import org.opends.server.api.PasswordStorageScheme; 040import org.opends.server.core.DirectoryServer; 041import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler; 042import org.opends.server.loggers.JDKLogging; 043import org.opends.server.schema.AuthPasswordSyntax; 044import org.opends.server.schema.UserPasswordSyntax; 045import org.opends.server.types.DirectoryException; 046import org.opends.server.types.InitializationException; 047import org.opends.server.types.NullOutputStream; 048import org.opends.server.util.BuildVersion; 049 050import com.forgerock.opendj.cli.ArgumentException; 051import com.forgerock.opendj.cli.ArgumentParser; 052import com.forgerock.opendj.cli.BooleanArgument; 053import com.forgerock.opendj.cli.FileBasedArgument; 054import com.forgerock.opendj.cli.StringArgument; 055 056/** 057 * This program provides a utility that may be used to interact with the 058 * password storage schemes defined in the Directory Server. In particular, 059 * it can encode a clear-text password using a specified scheme, and it can also 060 * determine whether a given encoded password is the encoded representation of a 061 * given clear-text password. Alternately, it can be used to obtain a list of 062 * the available password storage scheme names. 063 */ 064public class EncodePassword 065{ 066 /** 067 * Processes the command-line arguments and performs the requested action. 068 * 069 * @param args The command-line arguments provided to this program. 070 */ 071 public static void main(String[] args) 072 { 073 int returnCode = encodePassword(args, true, System.out, System.err); 074 if (returnCode != 0) 075 { 076 System.exit(filterExitCode(returnCode)); 077 } 078 } 079 080 /** 081 * Processes the command-line arguments and performs the requested action. 082 * 083 * @param args The command-line arguments provided to this program. 084 * 085 * @return An integer value that indicates whether processing was successful. 086 */ 087 public static int encodePassword(String[] args) 088 { 089 return encodePassword(args, true, System.out, System.err); 090 } 091 092 /** 093 * Processes the command-line arguments and performs the requested action. 094 * 095 * @param args The command-line arguments provided to this 096 * program. 097 * @param initializeServer Indicates whether to initialize the server. 098 * @param outStream The output stream to use for standard output, or 099 * <CODE>null</CODE> if standard output is not 100 * needed. 101 * @param errStream The output stream to use for standard error, or 102 * <CODE>null</CODE> if standard error is not 103 * needed. 104 * 105 * @return An integer value that indicates whether processing was successful. 106 */ 107 public static int encodePassword(String[] args, boolean initializeServer, 108 OutputStream outStream, 109 OutputStream errStream) 110 { 111 PrintStream out = NullOutputStream.wrapOrNullStream(outStream); 112 PrintStream err = NullOutputStream.wrapOrNullStream(errStream); 113 JDKLogging.disableLogging(); 114 115 // Define the command-line arguments that may be used with this program. 116 BooleanArgument authPasswordSyntax = null; 117 BooleanArgument useCompareResultCode = null; 118 BooleanArgument listSchemes = null; 119 BooleanArgument showUsage = null; 120 BooleanArgument interactivePassword = null; 121 StringArgument clearPassword = null; 122 FileBasedArgument clearPasswordFile = null; 123 StringArgument encodedPassword = null; 124 FileBasedArgument encodedPasswordFile = null; 125 StringArgument configFile = null; 126 StringArgument schemeName = null; 127 128 // Create the command-line argument parser for use with this program. 129 LocalizableMessage toolDescription = INFO_ENCPW_TOOL_DESCRIPTION.get(); 130 ArgumentParser argParser = 131 new ArgumentParser("org.opends.server.tools.EncodePassword", 132 toolDescription, false); 133 argParser.setShortToolDescription(REF_SHORT_DESC_ENCODE_PASSWORD.get()); 134 argParser.setVersionHandler(new DirectoryServerVersionHandler()); 135 136 // Initialize all the command-line argument types and register them with the parser. 137 try 138 { 139 listSchemes = 140 BooleanArgument.builder("listSchemes") 141 .shortIdentifier('l') 142 .description(INFO_ENCPW_DESCRIPTION_LISTSCHEMES.get()) 143 .buildAndAddToParser(argParser); 144 interactivePassword = 145 BooleanArgument.builder("interactivePassword") 146 .shortIdentifier('i') 147 .description(INFO_ENCPW_DESCRIPTION_INPUT_PW.get()) 148 .buildAndAddToParser(argParser); 149 clearPassword = 150 StringArgument.builder("clearPassword") 151 .shortIdentifier('c') 152 .description(INFO_ENCPW_DESCRIPTION_CLEAR_PW.get()) 153 .valuePlaceholder(INFO_CLEAR_PWD.get()) 154 .buildAndAddToParser(argParser); 155 clearPasswordFile = 156 FileBasedArgument.builder("clearPasswordFile") 157 .shortIdentifier('f') 158 .description(INFO_ENCPW_DESCRIPTION_CLEAR_PW_FILE.get()) 159 .valuePlaceholder(INFO_FILE_PLACEHOLDER.get()) 160 .buildAndAddToParser(argParser); 161 encodedPassword = 162 StringArgument.builder("encodedPassword") 163 .shortIdentifier('e') 164 .description(INFO_ENCPW_DESCRIPTION_ENCODED_PW.get()) 165 .valuePlaceholder(INFO_ENCODED_PWD_PLACEHOLDER.get()) 166 .buildAndAddToParser(argParser); 167 encodedPasswordFile = 168 FileBasedArgument.builder("encodedPasswordFile") 169 .shortIdentifier('E') 170 .description(INFO_ENCPW_DESCRIPTION_ENCODED_PW_FILE.get()) 171 .valuePlaceholder(INFO_FILE_PLACEHOLDER.get()) 172 .buildAndAddToParser(argParser); 173 configFile = 174 StringArgument.builder("configFile") 175 .shortIdentifier('F') 176 .description(INFO_DESCRIPTION_CONFIG_FILE.get()) 177 .hidden() 178 .required() 179 .valuePlaceholder(INFO_CONFIGFILE_PLACEHOLDER.get()) 180 .buildAndAddToParser(argParser); 181 schemeName = 182 StringArgument.builder("storageScheme") 183 .shortIdentifier('s') 184 .description(INFO_ENCPW_DESCRIPTION_SCHEME.get()) 185 .valuePlaceholder(INFO_STORAGE_SCHEME_PLACEHOLDER.get()) 186 .buildAndAddToParser(argParser); 187 authPasswordSyntax = 188 BooleanArgument.builder("authPasswordSyntax") 189 .shortIdentifier('a') 190 .description(INFO_ENCPW_DESCRIPTION_AUTHPW.get()) 191 .buildAndAddToParser(argParser); 192 useCompareResultCode = 193 BooleanArgument.builder("useCompareResultCode") 194 .shortIdentifier('r') 195 .description(INFO_ENCPW_DESCRIPTION_USE_COMPARE_RESULT.get()) 196 .buildAndAddToParser(argParser); 197 198 showUsage = showUsageArgument(); 199 argParser.addArgument(showUsage); 200 argParser.setUsageArgument(showUsage, out); 201 } 202 catch (ArgumentException ae) 203 { 204 printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage())); 205 return OPERATIONS_ERROR; 206 } 207 208 // Parse the command-line arguments provided to this program. 209 try 210 { 211 argParser.parseArguments(args); 212 } 213 catch (ArgumentException ae) 214 { 215 argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); 216 return OPERATIONS_ERROR; 217 } 218 219 // If we should just display usage or version information, 220 // then we've already done it so just return without doing anything else. 221 if (argParser.usageOrVersionDisplayed()) 222 { 223 return SUCCESS; 224 } 225 226 // Checks the version - if upgrade required, the tool is unusable 227 try 228 { 229 BuildVersion.checkVersionMismatch(); 230 } 231 catch (InitializationException e) 232 { 233 printWrappedText(err, e.getMessage()); 234 return 1; 235 } 236 237 // Check for conflicting arguments. 238 try 239 { 240 throwIfArgumentsConflict(clearPassword, clearPasswordFile); 241 throwIfArgumentsConflict(clearPassword, interactivePassword); 242 throwIfArgumentsConflict(clearPasswordFile, interactivePassword); 243 throwIfArgumentsConflict(encodedPassword, encodedPasswordFile); 244 } 245 catch (final ArgumentException conflict) 246 { 247 printWrappedText(err, conflict.getMessageObject()); 248 return OPERATIONS_ERROR; 249 } 250 251 // If we are not going to just list the storage schemes, then the clear-text 252 // password must have been provided. If we're going to encode a password, 253 // then the scheme must have also been provided. 254 if (!listSchemes.isPresent() 255 && !encodedPassword.isPresent() 256 && !encodedPasswordFile.isPresent() 257 && !schemeName.isPresent()) 258 { 259 argParser.displayMessageAndUsageReference(err, ERR_ENCPW_NO_SCHEME.get(schemeName.getLongIdentifier())); 260 return OPERATIONS_ERROR; 261 } 262 263 // Determine whether we're encoding the clear-text password or comparing it 264 // against an already-encoded password. 265 boolean compareMode; 266 ByteString encodedPW = null; 267 if (encodedPassword.hasValue()) 268 { 269 compareMode = true; 270 encodedPW = ByteString.valueOfUtf8(encodedPassword.getValue()); 271 } 272 else if (encodedPasswordFile.hasValue()) 273 { 274 compareMode = true; 275 encodedPW = ByteString.valueOfUtf8(encodedPasswordFile.getValue()); 276 } 277 else 278 { 279 compareMode = false; 280 } 281 282 if (initializeServer) 283 { 284 try 285 { 286 new DirectoryServer.InitializationBuilder(configFile.getValue()) 287 .requirePasswordStorageSchemes() 288 .initialize(); 289 } 290 catch (InitializationException ie) 291 { 292 printWrappedText(err, ERR_CANNOT_INITIALIZE_SERVER_COMPONENTS.get(getExceptionMessage(ie))); 293 return OPERATIONS_ERROR; 294 } 295 } 296 297 // If we are only trying to list the available schemes, then do so and exit. 298 if (listSchemes.isPresent()) 299 { 300 if (authPasswordSyntax.isPresent()) 301 { 302 listPasswordStorageSchemes(out, err, DirectoryServer.getAuthPasswordStorageSchemes().values(), true); 303 } 304 else 305 { 306 listPasswordStorageSchemes(out, err, DirectoryServer.getPasswordStorageSchemes(), false); 307 } 308 return SUCCESS; 309 } 310 311 // Either encode the clear-text password using the provided scheme, or 312 // compare the clear-text password against the encoded password. 313 ByteString clearPW = null; 314 if (compareMode) 315 { 316 // Check to see if the provided password value was encoded. If so, then 317 // break it down into its component parts and use that to perform the 318 // comparison. Otherwise, the user must have provided the storage scheme. 319 if (authPasswordSyntax.isPresent()) 320 { 321 String[] authPWElements; 322 try 323 { 324 authPWElements = AuthPasswordSyntax.decodeAuthPassword(encodedPW.toString()); 325 } 326 catch (DirectoryException de) 327 { 328 printWrappedText(err, ERR_ENCPW_INVALID_ENCODED_AUTHPW.get(de.getMessageObject())); 329 return OPERATIONS_ERROR; 330 } 331 catch (Exception e) 332 { 333 printWrappedText(err, ERR_ENCPW_INVALID_ENCODED_AUTHPW.get(e)); 334 return OPERATIONS_ERROR; 335 } 336 337 String scheme = authPWElements[0]; 338 String authInfo = authPWElements[1]; 339 String authValue = authPWElements[2]; 340 341 PasswordStorageScheme storageScheme = 342 DirectoryServer.getAuthPasswordStorageScheme(scheme); 343 if (storageScheme == null) 344 { 345 printWrappedText(err, ERR_ENCPW_NO_SUCH_AUTH_SCHEME.get(scheme)); 346 return OPERATIONS_ERROR; 347 } 348 349 if (clearPW == null) 350 { 351 clearPW = getClearPW(err, argParser, clearPassword, clearPasswordFile, interactivePassword); 352 if (clearPW == null) 353 { 354 return OPERATIONS_ERROR; 355 } 356 } 357 final boolean authPasswordMatches = 358 storageScheme.authPasswordMatches(clearPW, authInfo, authValue); 359 out.println(getOutputMessage(authPasswordMatches)); 360 if (useCompareResultCode.isPresent()) 361 { 362 return authPasswordMatches ? COMPARE_TRUE : COMPARE_FALSE; 363 } 364 return SUCCESS; 365 } 366 else 367 { 368 PasswordStorageScheme storageScheme; 369 String encodedPWString; 370 371 if (UserPasswordSyntax.isEncoded(encodedPW)) 372 { 373 try 374 { 375 String[] userPWElements = 376 UserPasswordSyntax.decodeUserPassword(encodedPW.toString()); 377 encodedPWString = userPWElements[1]; 378 379 storageScheme = 380 DirectoryServer.getPasswordStorageScheme(userPWElements[0]); 381 if (storageScheme == null) 382 { 383 printWrappedText(err, ERR_ENCPW_NO_SUCH_SCHEME.get(userPWElements[0])); 384 return OPERATIONS_ERROR; 385 } 386 } 387 catch (DirectoryException de) 388 { 389 printWrappedText(err, ERR_ENCPW_INVALID_ENCODED_USERPW.get(de.getMessageObject())); 390 return OPERATIONS_ERROR; 391 } 392 catch (Exception e) 393 { 394 printWrappedText(err, ERR_ENCPW_INVALID_ENCODED_USERPW.get(e)); 395 return OPERATIONS_ERROR; 396 } 397 } 398 else 399 { 400 if (! schemeName.isPresent()) 401 { 402 printWrappedText(err, ERR_ENCPW_NO_SCHEME.get(schemeName.getLongIdentifier())); 403 return OPERATIONS_ERROR; 404 } 405 406 encodedPWString = encodedPW.toString(); 407 408 String scheme = toLowerCase(schemeName.getValue()); 409 storageScheme = DirectoryServer.getPasswordStorageScheme(scheme); 410 if (storageScheme == null) 411 { 412 printWrappedText(err, ERR_ENCPW_NO_SUCH_SCHEME.get(scheme)); 413 return OPERATIONS_ERROR; 414 } 415 } 416 417 if (clearPW == null) 418 { 419 clearPW = getClearPW(err, argParser, clearPassword, clearPasswordFile, interactivePassword); 420 if (clearPW == null) 421 { 422 return OPERATIONS_ERROR; 423 } 424 } 425 boolean passwordMatches = 426 storageScheme.passwordMatches(clearPW, ByteString 427 .valueOfUtf8(encodedPWString)); 428 out.println(getOutputMessage(passwordMatches)); 429 if (useCompareResultCode.isPresent()) 430 { 431 return passwordMatches ? COMPARE_TRUE : COMPARE_FALSE; 432 } 433 return SUCCESS; 434 } 435 } 436 else 437 { 438 // Try to get a reference to the requested password storage scheme. 439 PasswordStorageScheme storageScheme; 440 if (authPasswordSyntax.isPresent()) 441 { 442 String scheme = schemeName.getValue(); 443 storageScheme = DirectoryServer.getAuthPasswordStorageScheme(scheme); 444 if (storageScheme == null) 445 { 446 printWrappedText(err, ERR_ENCPW_NO_SUCH_AUTH_SCHEME.get(scheme)); 447 return OPERATIONS_ERROR; 448 } 449 } 450 else 451 { 452 String scheme = toLowerCase(schemeName.getValue()); 453 storageScheme = DirectoryServer.getPasswordStorageScheme(scheme); 454 if (storageScheme == null) 455 { 456 printWrappedText(err, ERR_ENCPW_NO_SUCH_SCHEME.get(scheme)); 457 return OPERATIONS_ERROR; 458 } 459 } 460 461 if (authPasswordSyntax.isPresent()) 462 { 463 try 464 { 465 if (clearPW == null) 466 { 467 clearPW = getClearPW(err, argParser, clearPassword, clearPasswordFile, interactivePassword); 468 if (clearPW == null) 469 { 470 return OPERATIONS_ERROR; 471 } 472 } 473 encodedPW = storageScheme.encodeAuthPassword(clearPW); 474 475 LocalizableMessage message = ERR_ENCPW_ENCODED_PASSWORD.get(encodedPW); 476 out.println(message); 477 } 478 catch (DirectoryException de) 479 { 480 printWrappedText(err, ERR_ENCPW_CANNOT_ENCODE.get(de.getMessageObject())); 481 return OPERATIONS_ERROR; 482 } 483 catch (Exception e) 484 { 485 printWrappedText(err, ERR_ENCPW_CANNOT_ENCODE.get(getExceptionMessage(e))); 486 return OPERATIONS_ERROR; 487 } 488 } 489 else 490 { 491 try 492 { 493 if (clearPW == null) 494 { 495 clearPW = getClearPW(err, argParser, clearPassword, clearPasswordFile, interactivePassword); 496 if (clearPW == null) 497 { 498 return OPERATIONS_ERROR; 499 } 500 } 501 encodedPW = storageScheme.encodePasswordWithScheme(clearPW); 502 503 out.println(ERR_ENCPW_ENCODED_PASSWORD.get(encodedPW)); 504 } 505 catch (DirectoryException de) 506 { 507 printWrappedText(err, ERR_ENCPW_CANNOT_ENCODE.get(de.getMessageObject())); 508 return OPERATIONS_ERROR; 509 } 510 catch (Exception e) 511 { 512 printWrappedText(err, ERR_ENCPW_CANNOT_ENCODE.get(getExceptionMessage(e))); 513 return OPERATIONS_ERROR; 514 } 515 } 516 } 517 518 // If we've gotten here, then all processing completed successfully. 519 return SUCCESS; 520 } 521 522 private static void listPasswordStorageSchemes(PrintStream out, PrintStream err, 523 Collection<PasswordStorageScheme<?>> storageSchemes, boolean authPasswordSchemeName) 524 { 525 if (storageSchemes.isEmpty()) 526 { 527 printWrappedText(err, ERR_ENCPW_NO_STORAGE_SCHEMES.get()); 528 } 529 else 530 { 531 ArrayList<String> nameList = new ArrayList<>(storageSchemes.size()); 532 for (PasswordStorageScheme<?> s : storageSchemes) 533 { 534 if (authPasswordSchemeName) 535 { 536 nameList.add(s.getAuthPasswordSchemeName()); 537 } 538 else 539 { 540 nameList.add(s.getStorageSchemeName()); 541 } 542 } 543 Collections.sort(nameList); 544 545 for (String storageSchemeName : nameList) 546 { 547 out.println(storageSchemeName); 548 } 549 } 550 } 551 552 private static LocalizableMessage getOutputMessage(boolean passwordMatches) 553 { 554 if (passwordMatches) 555 { 556 return INFO_ENCPW_PASSWORDS_MATCH.get(); 557 } 558 return INFO_ENCPW_PASSWORDS_DO_NOT_MATCH.get(); 559 } 560 561 /** 562 * Get the clear password. 563 * @param err The error output. 564 * @param argParser The argument parser. 565 * @param clearPassword the clear password 566 * @param clearPasswordFile the file in which the password in stored 567 * @param interactivePassword indicate if the password should be asked 568 * interactively. 569 * @return the password or null if an error occurs. 570 */ 571 private static ByteString getClearPW(PrintStream err, 572 ArgumentParser argParser, StringArgument clearPassword, 573 FileBasedArgument clearPasswordFile, BooleanArgument interactivePassword) 574 { 575 if (clearPassword.hasValue()) 576 { 577 return ByteString.valueOfUtf8(clearPassword.getValue()); 578 } 579 else if (clearPasswordFile.hasValue()) 580 { 581 return ByteString.valueOfUtf8(clearPasswordFile.getValue()); 582 } 583 else if (interactivePassword.isPresent()) 584 { 585 try 586 { 587 EncodePassword encodePassword = new EncodePassword(); 588 String pwd1 = encodePassword.getPassword(INFO_ENCPW_INPUT_PWD_1.get().toString()); 589 String pwd2 = encodePassword.getPassword(INFO_ENCPW_INPUT_PWD_2.get().toString()); 590 if (pwd1.equals(pwd2)) 591 { 592 return ByteString.valueOfUtf8(pwd1); 593 } 594 else 595 { 596 printWrappedText(err, ERR_ENCPW_NOT_SAME_PW.get()); 597 return null; 598 } 599 } 600 catch (IOException e) 601 { 602 printWrappedText(err, ERR_ENCPW_CANNOT_READ_PW.get(e.getMessage())); 603 return null; 604 } 605 } 606 else 607 { 608 argParser.displayMessageAndUsageReference(err, ERR_ENCPW_NO_CLEAR_PW.get(clearPassword.getLongIdentifier(), 609 clearPasswordFile.getLongIdentifier(), interactivePassword.getLongIdentifier())); 610 return null; 611 } 612 } 613 614 /** 615 * Get the password from JDK6 console or from masked password. 616 * @param prompt The message to print out. 617 * @return the password 618 * @throws IOException if an issue occurs when reading the password 619 * from the input 620 */ 621 private String getPassword(String prompt) throws IOException 622 { 623 String password; 624 try 625 { 626 Console console = System.console(); 627 if (console == null) 628 { 629 throw new IOException("No console"); 630 } 631 password = new String(console.readPassword(prompt)); 632 } 633 catch (Exception e) 634 { 635 // Try the fallback to the old trick method. 636 // Create the thread that will erase chars 637 ErasingThread erasingThread = new ErasingThread(prompt); 638 erasingThread.start(); 639 640 password = ""; 641 642 // block until enter is pressed 643 while (true) 644 { 645 char c = (char) System.in.read(); 646 // assume enter pressed, stop masking 647 erasingThread.stopMasking(); 648 if (c == '\r') 649 { 650 c = (char) System.in.read(); 651 if (c == '\n') 652 { 653 break; 654 } 655 } 656 else if (c == '\n') 657 { 658 break; 659 } 660 else 661 { 662 // store the password 663 password += c; 664 } 665 } 666 } 667 return password; 668 } 669 670 /** Thread that mask user input. */ 671 private class ErasingThread extends Thread 672 { 673 private boolean stop; 674 private String prompt; 675 676 /** 677 * The class will mask the user input. 678 * @param prompt 679 * The prompt displayed to the user 680 */ 681 public ErasingThread(String prompt) 682 { 683 this.prompt = prompt; 684 } 685 686 /** Begin masking until asked to stop. */ 687 @Override 688 public void run() 689 { 690 while (!stop) 691 { 692 try 693 { 694 // attempt masking at this rate 695 Thread.sleep(1); 696 } 697 catch (InterruptedException iex) 698 { 699 iex.printStackTrace(); 700 } 701 if (!stop) 702 { 703 System.out.print("\r" + prompt + " \r" + prompt); 704 } 705 System.out.flush(); 706 } 707 } 708 709 /** Instruct the thread to stop masking. */ 710 public void stopMasking() 711 { 712 this.stop = true; 713 } 714 } 715}