001 /* 002 * Copyright 2013-2015 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005 /* 006 * Copyright (C) 2015 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021 package com.unboundid.ldap.sdk.unboundidds; 022 023 024 025 import java.io.OutputStream; 026 import java.io.Serializable; 027 import java.util.ArrayList; 028 import java.util.LinkedHashMap; 029 import java.util.List; 030 031 import com.unboundid.ldap.sdk.LDAPConnection; 032 import com.unboundid.ldap.sdk.LDAPException; 033 import com.unboundid.ldap.sdk.ResultCode; 034 import com.unboundid.ldap.sdk.Version; 035 import com.unboundid.ldap.sdk.unboundidds.extensions. 036 DeliverOneTimePasswordExtendedRequest; 037 import com.unboundid.ldap.sdk.unboundidds.extensions. 038 DeliverOneTimePasswordExtendedResult; 039 import com.unboundid.util.Debug; 040 import com.unboundid.util.LDAPCommandLineTool; 041 import com.unboundid.util.ObjectPair; 042 import com.unboundid.util.PasswordReader; 043 import com.unboundid.util.StaticUtils; 044 import com.unboundid.util.ThreadSafety; 045 import com.unboundid.util.ThreadSafetyLevel; 046 import com.unboundid.util.args.ArgumentException; 047 import com.unboundid.util.args.ArgumentParser; 048 import com.unboundid.util.args.BooleanArgument; 049 import com.unboundid.util.args.DNArgument; 050 import com.unboundid.util.args.FileArgument; 051 import com.unboundid.util.args.StringArgument; 052 053 import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*; 054 055 056 057 /** 058 * <BLOCKQUOTE> 059 * <B>NOTE:</B> This class is part of the Commercial Edition of the UnboundID 060 * LDAP SDK for Java. It is not available for use in applications that 061 * include only the Standard Edition of the LDAP SDK, and is not supported for 062 * use in conjunction with non-UnboundID products. 063 * </BLOCKQUOTE> 064 * This class provides a utility that may be used to request that the Directory 065 * Server deliver a one-time password to a user through some out-of-band 066 * mechanism. 067 */ 068 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 069 public final class DeliverOneTimePassword 070 extends LDAPCommandLineTool 071 implements Serializable 072 { 073 /** 074 * The serial version UID for this serializable class. 075 */ 076 private static final long serialVersionUID = -7414730592661321416L; 077 078 079 080 // Indicates that the tool should interactively prompt the user for their 081 // bind password. 082 private BooleanArgument promptForBindPassword; 083 084 // The DN for the user to whom the one-time password should be delivered. 085 private DNArgument bindDN; 086 087 // The path to a file containing the static password for the user to whom the 088 // one-time password should be delivered. 089 private FileArgument bindPasswordFile; 090 091 // The text to include after the one-time password in the "compact" message. 092 private StringArgument compactTextAfterOTP; 093 094 // The text to include before the one-time password in the "compact" message. 095 private StringArgument compactTextBeforeOTP; 096 097 // The name of the mechanism through which the one-time password should be 098 // delivered. 099 private StringArgument deliveryMechanism; 100 101 // The text to include after the one-time password in the "full" message. 102 private StringArgument fullTextAfterOTP; 103 104 // The text to include before the one-time password in the "full" message. 105 private StringArgument fullTextBeforeOTP; 106 107 // The subject to use for the message containing the delivered token. 108 private StringArgument messageSubject; 109 110 // The username for the user to whom the one-time password should be 111 // delivered. 112 private StringArgument userName; 113 114 // The static password for the user to whom the one-time password should be 115 // delivered. 116 private StringArgument bindPassword; 117 118 119 120 /** 121 * Parse the provided command line arguments and perform the appropriate 122 * processing. 123 * 124 * @param args The command line arguments provided to this program. 125 */ 126 public static void main(final String... args) 127 { 128 final ResultCode resultCode = main(args, System.out, System.err); 129 if (resultCode != ResultCode.SUCCESS) 130 { 131 System.exit(resultCode.intValue()); 132 } 133 } 134 135 136 137 /** 138 * Parse the provided command line arguments and perform the appropriate 139 * processing. 140 * 141 * @param args The command line arguments provided to this program. 142 * @param outStream The output stream to which standard out should be 143 * written. It may be {@code null} if output should be 144 * suppressed. 145 * @param errStream The output stream to which standard error should be 146 * written. It may be {@code null} if error messages 147 * should be suppressed. 148 * 149 * @return A result code indicating whether the processing was successful. 150 */ 151 public static ResultCode main(final String[] args, 152 final OutputStream outStream, 153 final OutputStream errStream) 154 { 155 final DeliverOneTimePassword tool = 156 new DeliverOneTimePassword(outStream, errStream); 157 return tool.runTool(args); 158 } 159 160 161 162 /** 163 * Creates a new instance of this tool. 164 * 165 * @param outStream The output stream to which standard out should be 166 * written. It may be {@code null} if output should be 167 * suppressed. 168 * @param errStream The output stream to which standard error should be 169 * written. It may be {@code null} if error messages 170 * should be suppressed. 171 */ 172 public DeliverOneTimePassword(final OutputStream outStream, 173 final OutputStream errStream) 174 { 175 super(outStream, errStream); 176 177 promptForBindPassword = null; 178 bindDN = null; 179 bindPasswordFile = null; 180 bindPassword = null; 181 compactTextAfterOTP = null; 182 compactTextBeforeOTP = null; 183 deliveryMechanism = null; 184 fullTextAfterOTP = null; 185 fullTextBeforeOTP = null; 186 messageSubject = null; 187 userName = null; 188 } 189 190 191 192 /** 193 * {@inheritDoc} 194 */ 195 @Override() 196 public String getToolName() 197 { 198 return "deliver-one-time-password"; 199 } 200 201 202 203 /** 204 * {@inheritDoc} 205 */ 206 @Override() 207 public String getToolDescription() 208 { 209 return INFO_DELIVER_OTP_TOOL_DESCRIPTION.get(); 210 } 211 212 213 214 /** 215 * {@inheritDoc} 216 */ 217 @Override() 218 public String getToolVersion() 219 { 220 return Version.NUMERIC_VERSION_STRING; 221 } 222 223 224 225 /** 226 * {@inheritDoc} 227 */ 228 @Override() 229 public void addNonLDAPArguments(final ArgumentParser parser) 230 throws ArgumentException 231 { 232 bindDN = new DNArgument('D', "bindDN", false, 1, 233 INFO_DELIVER_OTP_PLACEHOLDER_DN.get(), 234 INFO_DELIVER_OTP_DESCRIPTION_BIND_DN.get()); 235 parser.addArgument(bindDN); 236 237 userName = new StringArgument('n', "userName", false, 1, 238 INFO_DELIVER_OTP_PLACEHOLDER_USERNAME.get(), 239 INFO_DELIVER_OTP_DESCRIPTION_USERNAME.get()); 240 parser.addArgument(userName); 241 242 bindPassword = new StringArgument('w', "bindPassword", false, 1, 243 INFO_DELIVER_OTP_PLACEHOLDER_PASSWORD.get(), 244 INFO_DELIVER_OTP_DESCRIPTION_BIND_PW.get()); 245 parser.addArgument(bindPassword); 246 247 bindPasswordFile = new FileArgument('j', "bindPasswordFile", false, 1, 248 INFO_DELIVER_OTP_PLACEHOLDER_PATH.get(), 249 INFO_DELIVER_OTP_DESCRIPTION_BIND_PW_FILE.get(), true, true, true, 250 false); 251 parser.addArgument(bindPasswordFile); 252 253 promptForBindPassword = new BooleanArgument(null, "promptForBindPassword", 254 1, INFO_DELIVER_OTP_DESCRIPTION_BIND_PW_PROMPT.get()); 255 parser.addArgument(promptForBindPassword); 256 257 deliveryMechanism = new StringArgument('m', "deliveryMechanism", false, 0, 258 INFO_DELIVER_OTP_PLACEHOLDER_NAME.get(), 259 INFO_DELIVER_OTP_DESCRIPTION_MECH.get()); 260 parser.addArgument(deliveryMechanism); 261 262 messageSubject = new StringArgument('s', "messageSubject", false, 1, 263 INFO_DELIVER_OTP_PLACEHOLDER_SUBJECT.get(), 264 INFO_DELIVER_OTP_DESCRIPTION_SUBJECT.get()); 265 parser.addArgument(messageSubject); 266 267 fullTextBeforeOTP = new StringArgument('f', "fullTextBeforeOTP", false, 268 1, INFO_DELIVER_OTP_PLACEHOLDER_FULL_BEFORE.get(), 269 INFO_DELIVER_OTP_DESCRIPTION_FULL_BEFORE.get()); 270 parser.addArgument(fullTextBeforeOTP); 271 272 fullTextAfterOTP = new StringArgument('F', "fullTextAfterOTP", false, 273 1, INFO_DELIVER_OTP_PLACEHOLDER_FULL_AFTER.get(), 274 INFO_DELIVER_OTP_DESCRIPTION_FULL_AFTER.get()); 275 parser.addArgument(fullTextAfterOTP); 276 277 compactTextBeforeOTP = new StringArgument('c', "compactTextBeforeOTP", 278 false, 1, INFO_DELIVER_OTP_PLACEHOLDER_COMPACT_BEFORE.get(), 279 INFO_DELIVER_OTP_DESCRIPTION_COMPACT_BEFORE.get()); 280 parser.addArgument(compactTextBeforeOTP); 281 282 compactTextAfterOTP = new StringArgument('C', "compactTextAfterOTP", 283 false, 1, INFO_DELIVER_OTP_PLACEHOLDER_COMPACT_AFTER.get(), 284 INFO_DELIVER_OTP_DESCRIPTION_COMPACT_AFTER.get()); 285 parser.addArgument(compactTextAfterOTP); 286 287 288 // Either the bind DN or username must have been provided. 289 parser.addRequiredArgumentSet(bindDN, userName); 290 291 // Only one option may be used for specifying the user identity. 292 parser.addExclusiveArgumentSet(bindDN, userName); 293 294 // Only one option may be used for specifying the bind password. 295 parser.addExclusiveArgumentSet(bindPassword, bindPasswordFile, 296 promptForBindPassword); 297 } 298 299 300 301 /** 302 * {@inheritDoc} 303 */ 304 @Override() 305 protected boolean supportsAuthentication() 306 { 307 return false; 308 } 309 310 311 312 /** 313 * {@inheritDoc} 314 */ 315 @Override() 316 public ResultCode doToolProcessing() 317 { 318 // Construct the authentication identity. 319 final String authID; 320 if (bindDN.isPresent()) 321 { 322 authID = "dn:" + bindDN.getValue(); 323 } 324 else 325 { 326 authID = "u:" + userName.getValue(); 327 } 328 329 330 // Get the bind password. 331 final String pw; 332 if (bindPassword.isPresent()) 333 { 334 pw = bindPassword.getValue(); 335 } 336 else if (bindPasswordFile.isPresent()) 337 { 338 try 339 { 340 pw = bindPasswordFile.getNonBlankFileLines().get(0); 341 } 342 catch (Exception e) 343 { 344 Debug.debugException(e); 345 err(ERR_DELIVER_OTP_CANNOT_READ_BIND_PW.get( 346 StaticUtils.getExceptionMessage(e))); 347 return ResultCode.LOCAL_ERROR; 348 } 349 } 350 else 351 { 352 try 353 { 354 getOut().print(INFO_DELIVER_OTP_ENTER_PW.get()); 355 pw = StaticUtils.toUTF8String(PasswordReader.readPassword()); 356 getOut().println(); 357 } 358 catch (final Exception e) 359 { 360 Debug.debugException(e); 361 err(ERR_DELIVER_OTP_CANNOT_READ_BIND_PW.get( 362 StaticUtils.getExceptionMessage(e))); 363 return ResultCode.LOCAL_ERROR; 364 } 365 } 366 367 368 // Get the set of preferred delivery mechanisms. 369 final ArrayList<ObjectPair<String,String>> preferredDeliveryMechanisms; 370 if (deliveryMechanism.isPresent()) 371 { 372 final List<String> dmList = deliveryMechanism.getValues(); 373 preferredDeliveryMechanisms = 374 new ArrayList<ObjectPair<String,String>>(dmList.size()); 375 for (final String s : dmList) 376 { 377 preferredDeliveryMechanisms.add(new ObjectPair<String,String>(s, null)); 378 } 379 } 380 else 381 { 382 preferredDeliveryMechanisms = null; 383 } 384 385 386 // Get a connection to the directory server. 387 final LDAPConnection conn; 388 try 389 { 390 conn = getConnection(); 391 } 392 catch (final LDAPException le) 393 { 394 Debug.debugException(le); 395 err(ERR_DELIVER_OTP_CANNOT_GET_CONNECTION.get( 396 StaticUtils.getExceptionMessage(le))); 397 return le.getResultCode(); 398 } 399 400 try 401 { 402 // Create and send the extended request 403 final DeliverOneTimePasswordExtendedRequest request = 404 new DeliverOneTimePasswordExtendedRequest(authID, pw, 405 messageSubject.getValue(), fullTextBeforeOTP.getValue(), 406 fullTextAfterOTP.getValue(), compactTextBeforeOTP.getValue(), 407 compactTextAfterOTP.getValue(), preferredDeliveryMechanisms); 408 final DeliverOneTimePasswordExtendedResult result; 409 try 410 { 411 result = (DeliverOneTimePasswordExtendedResult) 412 conn.processExtendedOperation(request); 413 } 414 catch (final LDAPException le) 415 { 416 Debug.debugException(le); 417 err(ERR_DELIVER_OTP_ERROR_PROCESSING_EXTOP.get( 418 StaticUtils.getExceptionMessage(le))); 419 return le.getResultCode(); 420 } 421 422 if (result.getResultCode() == ResultCode.SUCCESS) 423 { 424 final String mechanism = result.getDeliveryMechanism(); 425 final String id = result.getRecipientID(); 426 if (id == null) 427 { 428 out(INFO_DELIVER_OTP_SUCCESS_RESULT_WITHOUT_ID.get(mechanism)); 429 } 430 else 431 { 432 out(INFO_DELIVER_OTP_SUCCESS_RESULT_WITH_ID.get(mechanism, id)); 433 } 434 435 final String message = result.getDeliveryMessage(); 436 if (message != null) 437 { 438 out(INFO_DELIVER_OTP_SUCCESS_MESSAGE.get(message)); 439 } 440 } 441 else 442 { 443 if (result.getDiagnosticMessage() == null) 444 { 445 err(ERR_DELIVER_OTP_ERROR_RESULT_NO_MESSAGE.get( 446 String.valueOf(result.getResultCode()))); 447 } 448 else 449 { 450 err(ERR_DELIVER_OTP_ERROR_RESULT.get( 451 String.valueOf(result.getResultCode()), 452 result.getDiagnosticMessage())); 453 } 454 } 455 456 return result.getResultCode(); 457 } 458 finally 459 { 460 conn.close(); 461 } 462 } 463 464 465 466 /** 467 * {@inheritDoc} 468 */ 469 @Override() 470 public LinkedHashMap<String[],String> getExampleUsages() 471 { 472 final LinkedHashMap<String[],String> exampleMap = 473 new LinkedHashMap<String[],String>(2); 474 475 String[] args = 476 { 477 "--hostname", "server.example.com", 478 "--port", "389", 479 "--bindDN", "uid=test.user,ou=People,dc=example,dc=com", 480 "--bindPassword", "password", 481 "--messageSubject", "Your one-time password", 482 "--fullTextBeforeOTP", "Your one-time password is '", 483 "--fullTextAfterOTP", "'.", 484 "--compactTextBeforeOTP", "Your OTP is '", 485 "--compactTextAfterOTP", "'.", 486 }; 487 exampleMap.put(args, 488 INFO_DELIVER_OTP_EXAMPLE_1.get()); 489 490 args = new String[] 491 { 492 "--hostname", "server.example.com", 493 "--port", "389", 494 "--userName", "test.user", 495 "--bindPassword", "password", 496 "--deliveryMechanism", "SMS", 497 "--deliveryMechanism", "E-Mail", 498 "--messageSubject", "Your one-time password", 499 "--fullTextBeforeOTP", "Your one-time password is '", 500 "--fullTextAfterOTP", "'.", 501 "--compactTextBeforeOTP", "Your OTP is '", 502 "--compactTextAfterOTP", "'.", 503 }; 504 exampleMap.put(args, 505 INFO_DELIVER_OTP_EXAMPLE_2.get()); 506 507 return exampleMap; 508 } 509 }