001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2009-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2013-2015 ForgeRock AS 026 */ 027package org.opends.server.tools; 028import java.io.IOException; 029import java.io.PrintStream; 030import java.net.ConnectException; 031import java.net.InetAddress; 032import java.net.Socket; 033import java.net.SocketException; 034import java.net.UnknownHostException; 035import java.util.ArrayList; 036import java.util.concurrent.atomic.AtomicInteger; 037import java.util.logging.Level; 038 039import org.forgerock.i18n.LocalizableMessage; 040import org.opends.server.controls.AuthorizationIdentityResponseControl; 041import org.opends.server.controls.PasswordExpiringControl; 042import org.opends.server.controls.PasswordPolicyErrorType; 043import org.opends.server.controls.PasswordPolicyResponseControl; 044import org.opends.server.controls.PasswordPolicyWarningType; 045import org.opends.server.loggers.JDKLogging; 046import org.forgerock.i18n.slf4j.LocalizedLogger; 047import org.opends.server.protocols.ldap.ExtendedRequestProtocolOp; 048import org.opends.server.protocols.ldap.ExtendedResponseProtocolOp; 049import org.opends.server.protocols.ldap.LDAPControl; 050import org.opends.server.protocols.ldap.LDAPMessage; 051import org.opends.server.protocols.ldap.UnbindRequestProtocolOp; 052import org.forgerock.opendj.ldap.ByteString; 053import org.opends.server.types.Control; 054import org.opends.server.types.DirectoryException; 055import org.opends.server.types.LDAPException; 056 057import com.forgerock.opendj.cli.ClientException; 058import static org.opends.messages.CoreMessages.*; 059import static org.opends.messages.ToolMessages.*; 060import static org.opends.server.protocols.ldap.LDAPResultCode.*; 061import static org.opends.server.util.ServerConstants.*; 062import static org.opends.server.util.StaticUtils.*; 063 064 065 066/** 067 * This class provides a tool that can be used to issue search requests to the 068 * Directory Server. 069 */ 070public class LDAPConnection 071{ 072 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 073 074 /** The hostname to connect to. */ 075 private String hostName; 076 077 /** The port number on which the directory server is accepting requests. */ 078 private int portNumber = 389; 079 080 private LDAPConnectionOptions connectionOptions; 081 private LDAPWriter ldapWriter; 082 private LDAPReader ldapReader; 083 private int versionNumber = 3; 084 085 private final PrintStream out; 086 private final PrintStream err; 087 088 /** 089 * Constructor for the LDAPConnection object. 090 * 091 * @param host The hostname to send the request to. 092 * @param port The port number on which the directory server is accepting 093 * requests. 094 * @param options The set of options for this connection. 095 */ 096 public LDAPConnection(String host, int port, LDAPConnectionOptions options) 097 { 098 this(host, port, options, System.out, System.err); 099 } 100 101 /** 102 * Constructor for the LDAPConnection object. 103 * 104 * @param host The hostname to send the request to. 105 * @param port The port number on which the directory server is accepting 106 * requests. 107 * @param options The set of options for this connection. 108 * @param out The print stream to use for standard output. 109 * @param err The print stream to use for standard error. 110 */ 111 public LDAPConnection(String host, int port, LDAPConnectionOptions options, 112 PrintStream out, PrintStream err) 113 { 114 this.hostName = host; 115 this.portNumber = port; 116 this.connectionOptions = options; 117 this.versionNumber = options.getVersionNumber(); 118 this.out = out; 119 this.err = err; 120 } 121 122 /** 123 * Connects to the directory server instance running on specified hostname 124 * and port number. 125 * 126 * @param bindDN The DN to bind with. 127 * @param bindPassword The password to bind with. 128 * 129 * @throws LDAPConnectionException If a problem occurs while attempting to 130 * establish the connection to the server. 131 */ 132 public void connectToHost(String bindDN, String bindPassword) 133 throws LDAPConnectionException 134 { 135 connectToHost(bindDN, bindPassword, new AtomicInteger(1)); 136 } 137 138 /** 139 * Connects to the directory server instance running on specified hostname 140 * and port number. 141 * 142 * @param bindDN The DN to bind with. 143 * @param bindPassword The password to bind with. 144 * @param nextMessageID The message ID counter that should be used for 145 * operations performed while establishing the 146 * connection. 147 * 148 * @throws LDAPConnectionException If a problem occurs while attempting to 149 * establish the connection to the server. 150 */ 151 public void connectToHost(String bindDN, String bindPassword, 152 AtomicInteger nextMessageID) 153 throws LDAPConnectionException 154 { 155 connectToHost(bindDN, bindPassword, nextMessageID, 0); 156 } 157 158 /** 159 * Connects to the directory server instance running on specified hostname 160 * and port number. 161 * 162 * @param bindDN The DN to bind with. 163 * @param bindPassword The password to bind with. 164 * @param nextMessageID The message ID counter that should be used for 165 * operations performed while establishing the 166 * connection. 167 * @param timeout The timeout to connect to the specified host. The 168 * timeout is the timeout at the socket level in 169 * milliseconds. If the timeout value is {@code 0}, 170 * no timeout is used. 171 * 172 * @throws LDAPConnectionException If a problem occurs while attempting to 173 * establish the connection to the server. 174 */ 175 public void connectToHost(String bindDN, String bindPassword, 176 AtomicInteger nextMessageID, int timeout) 177 throws LDAPConnectionException 178 { 179 Socket socket; 180 Socket startTLSSocket = null; 181 int resultCode; 182 ArrayList<Control> requestControls = new ArrayList<> (); 183 ArrayList<Control> responseControls = new ArrayList<> (); 184 185 if (connectionOptions.isVerbose()) 186 { 187 JDKLogging.enableConsoleLoggingForOpenDJ(Level.ALL); 188 } 189 else 190 { 191 JDKLogging.disableLogging(); 192 } 193 194 195 if(connectionOptions.useStartTLS()) 196 { 197 try 198 { 199 startTLSSocket = createSocket(); 200 ldapWriter = new LDAPWriter(startTLSSocket); 201 ldapReader = new LDAPReader(startTLSSocket); 202 } 203 catch (LDAPConnectionException e) 204 { 205 throw e; 206 } 207 catch (Exception ex) 208 { 209 logger.traceException(ex); 210 throw new LDAPConnectionException(LocalizableMessage.raw(ex.getMessage()), ex); 211 } 212 213 // Send the StartTLS extended request. 214 ExtendedRequestProtocolOp extendedRequest = 215 new ExtendedRequestProtocolOp(OID_START_TLS_REQUEST); 216 217 LDAPMessage msg = new LDAPMessage(nextMessageID.getAndIncrement(), 218 extendedRequest); 219 try 220 { 221 ldapWriter.writeMessage(msg); 222 223 // Read the response from the server. 224 msg = ldapReader.readMessage(); 225 }catch (LDAPException ex1) 226 { 227 logger.traceException(ex1); 228 throw new LDAPConnectionException(LocalizableMessage.raw(ex1.getMessage()), ex1 229 .getResultCode(), null, ex1); 230 } catch (Exception ex1) 231 { 232 logger.traceException(ex1); 233 throw new LDAPConnectionException(LocalizableMessage.raw(ex1.getMessage()), ex1); 234 } 235 ExtendedResponseProtocolOp res = msg.getExtendedResponseProtocolOp(); 236 resultCode = res.getResultCode(); 237 if(resultCode != SUCCESS) 238 { 239 throw new LDAPConnectionException(res.getErrorMessage(), 240 resultCode, 241 res.getErrorMessage(), 242 res.getMatchedDN(), null); 243 } 244 } 245 SSLConnectionFactory sslConnectionFactory = 246 connectionOptions.getSSLConnectionFactory(); 247 try 248 { 249 socket = createSSLOrBasicSocket(startTLSSocket, sslConnectionFactory); 250 ldapWriter = new LDAPWriter(socket); 251 ldapReader = new LDAPReader(socket); 252 } catch(UnknownHostException | ConnectException e) 253 { 254 LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 255 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, e); 256 } catch (LDAPConnectionException e) 257 { 258 throw e; 259 } catch(Exception ex2) 260 { 261 logger.traceException(ex2); 262 throw new LDAPConnectionException(LocalizableMessage.raw(ex2.getMessage()), ex2); 263 } 264 265 // We need this so that we don't run out of addresses when the tool 266 // commands are called A LOT, as in the unit tests. 267 try 268 { 269 socket.setSoLinger(true, 1); 270 socket.setReuseAddress(true); 271 if (timeout > 0) 272 { 273 socket.setSoTimeout(timeout); 274 } 275 } catch(IOException e) 276 { 277 logger.traceException(e); 278 // It doesn't matter too much if this throws, so ignore it. 279 } 280 281 if (connectionOptions.getReportAuthzID()) 282 { 283 requestControls.add(new LDAPControl(OID_AUTHZID_REQUEST)); 284 } 285 286 if (connectionOptions.usePasswordPolicyControl()) 287 { 288 requestControls.add(new LDAPControl(OID_PASSWORD_POLICY_CONTROL)); 289 } 290 291 LDAPAuthenticationHandler handler = new LDAPAuthenticationHandler( 292 ldapReader, ldapWriter, hostName, nextMessageID); 293 try 294 { 295 ByteString bindDNBytes; 296 if(bindDN == null) 297 { 298 bindDNBytes = ByteString.empty(); 299 } 300 else 301 { 302 bindDNBytes = ByteString.valueOfUtf8(bindDN); 303 } 304 305 ByteString bindPW; 306 if (bindPassword == null) 307 { 308 bindPW = null; 309 } 310 else 311 { 312 bindPW = ByteString.valueOfUtf8(bindPassword); 313 } 314 315 String result = null; 316 if (connectionOptions.useSASLExternal()) 317 { 318 result = handler.doSASLExternal(bindDNBytes, 319 connectionOptions.getSASLProperties(), 320 requestControls, responseControls); 321 } 322 else if (connectionOptions.getSASLMechanism() != null) 323 { 324 result = handler.doSASLBind(bindDNBytes, bindPW, 325 connectionOptions.getSASLMechanism(), 326 connectionOptions.getSASLProperties(), 327 requestControls, responseControls); 328 } 329 else if(bindDN != null) 330 { 331 result = handler.doSimpleBind(versionNumber, bindDNBytes, bindPW, 332 requestControls, responseControls); 333 } 334 if(result != null) 335 { 336 out.println(result); 337 } 338 339 for (Control c : responseControls) 340 { 341 if (c.getOID().equals(OID_AUTHZID_RESPONSE)) 342 { 343 AuthorizationIdentityResponseControl control; 344 if (c instanceof LDAPControl) 345 { 346 // We have to decode this control. 347 control = AuthorizationIdentityResponseControl.DECODER.decode(c 348 .isCritical(), ((LDAPControl) c).getValue()); 349 } 350 else 351 { 352 // Control should already have been decoded. 353 control = (AuthorizationIdentityResponseControl)c; 354 } 355 356 LocalizableMessage message = 357 INFO_BIND_AUTHZID_RETURNED.get( 358 control.getAuthorizationID()); 359 out.println(message); 360 } 361 else if (c.getOID().equals(OID_NS_PASSWORD_EXPIRED)) 362 { 363 LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRED.get(); 364 out.println(message); 365 } 366 else if (c.getOID().equals(OID_NS_PASSWORD_EXPIRING)) 367 { 368 PasswordExpiringControl control; 369 if(c instanceof LDAPControl) 370 { 371 // We have to decode this control. 372 control = PasswordExpiringControl.DECODER.decode(c.isCritical(), 373 ((LDAPControl) c).getValue()); 374 } 375 else 376 { 377 // Control should already have been decoded. 378 control = (PasswordExpiringControl)c; 379 } 380 LocalizableMessage timeString = 381 secondsToTimeString(control.getSecondsUntilExpiration()); 382 383 384 LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRING.get(timeString); 385 out.println(message); 386 } 387 else if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL)) 388 { 389 PasswordPolicyResponseControl pwPolicyControl; 390 if(c instanceof LDAPControl) 391 { 392 pwPolicyControl = PasswordPolicyResponseControl.DECODER.decode(c 393 .isCritical(), ((LDAPControl) c).getValue()); 394 } 395 else 396 { 397 pwPolicyControl = (PasswordPolicyResponseControl)c; 398 } 399 400 401 PasswordPolicyErrorType errorType = pwPolicyControl.getErrorType(); 402 if (errorType != null) 403 { 404 switch (errorType) 405 { 406 case PASSWORD_EXPIRED: 407 408 LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRED.get(); 409 out.println(message); 410 break; 411 case ACCOUNT_LOCKED: 412 413 message = INFO_BIND_ACCOUNT_LOCKED.get(); 414 out.println(message); 415 break; 416 case CHANGE_AFTER_RESET: 417 418 message = INFO_BIND_MUST_CHANGE_PASSWORD.get(); 419 out.println(message); 420 break; 421 } 422 } 423 424 PasswordPolicyWarningType warningType = 425 pwPolicyControl.getWarningType(); 426 if (warningType != null) 427 { 428 switch (warningType) 429 { 430 case TIME_BEFORE_EXPIRATION: 431 LocalizableMessage timeString = 432 secondsToTimeString(pwPolicyControl.getWarningValue()); 433 434 435 LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRING.get(timeString); 436 out.println(message); 437 break; 438 case GRACE_LOGINS_REMAINING: 439 440 message = INFO_BIND_GRACE_LOGINS_REMAINING.get( 441 pwPolicyControl.getWarningValue()); 442 out.println(message); 443 break; 444 } 445 } 446 } 447 } 448 } catch(ClientException ce) 449 { 450 logger.traceException(ce); 451 throw new LDAPConnectionException(ce.getMessageObject(), ce.getReturnCode(), 452 null, ce); 453 } catch (LDAPException le) { 454 throw new LDAPConnectionException(le.getMessageObject(), 455 le.getResultCode(), 456 le.getErrorMessage(), 457 le.getMatchedDN(), 458 le.getCause()); 459 } catch (DirectoryException de) 460 { 461 throw new LDAPConnectionException(de.getMessageObject(), 462 de.getResultCode().intValue(), null, de.getMatchedDN(), de.getCause()); 463 } catch(Exception ex) 464 { 465 logger.traceException(ex); 466 throw new LDAPConnectionException( 467 LocalizableMessage.raw(ex.getLocalizedMessage()),ex); 468 } 469 finally 470 { 471 if (timeout > 0) 472 { 473 try 474 { 475 socket.setSoTimeout(0); 476 } 477 catch (SocketException e) 478 { 479 e.printStackTrace(); 480 logger.traceException(e); 481 } 482 } 483 } 484 485 } 486 487 /** 488 * Creates a socket using the hostName and portNumber encapsulated in the 489 * current object. For each IP address associated to this host name, 490 * createSocket() will try to open a socket and it will return the first 491 * socket for which we successfully establish a connection. 492 * <p> 493 * This method can never return null because it will receive 494 * UnknownHostException before and then throw LDAPConnectionException. 495 * </p> 496 * 497 * @return a new {@link Socket}. 498 * @throws LDAPConnectionException 499 * if any exception occurs including UnknownHostException 500 */ 501 private Socket createSocket() throws LDAPConnectionException 502 { 503 ConnectException ce = null; 504 try 505 { 506 for (InetAddress inetAddress : InetAddress.getAllByName(hostName)) 507 { 508 try 509 { 510 return new Socket(inetAddress, portNumber); 511 } 512 catch (ConnectException ce2) 513 { 514 if (ce == null) 515 { 516 ce = ce2; 517 } 518 } 519 } 520 } 521 catch (UnknownHostException uhe) 522 { 523 LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 524 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, 525 uhe); 526 } 527 catch (Exception ex) 528 { 529 // if we get there, something went awfully wrong while creatng one socket, 530 // no need to continue the for loop. 531 logger.traceException(ex); 532 throw new LDAPConnectionException(LocalizableMessage.raw(ex.getMessage()), ex); 533 } 534 if (ce != null) 535 { 536 LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 537 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, 538 ce); 539 } 540 return null; 541 } 542 543 /** 544 * Creates an SSL socket using the hostName and portNumber encapsulated in the 545 * current object. For each IP address associated to this host name, 546 * createSSLSocket() will try to open a socket and it will return the first 547 * socket for which we successfully establish a connection. 548 * <p> 549 * This method can never return null because it will receive 550 * UnknownHostException before and then throw LDAPConnectionException. 551 * </p> 552 * 553 * @return a new {@link Socket}. 554 * @throws LDAPConnectionException 555 * if any exception occurs including UnknownHostException 556 */ 557 private Socket createSSLSocket(SSLConnectionFactory sslConnectionFactory) 558 throws SSLConnectionException, LDAPConnectionException 559 { 560 ConnectException ce = null; 561 try 562 { 563 for (InetAddress inetAddress : InetAddress.getAllByName(hostName)) 564 { 565 try 566 { 567 return sslConnectionFactory.createSocket(inetAddress, portNumber); 568 } 569 catch (ConnectException ce2) 570 { 571 if (ce == null) 572 { 573 ce = ce2; 574 } 575 } 576 } 577 } 578 catch (UnknownHostException uhe) 579 { 580 LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 581 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, 582 uhe); 583 } 584 catch (Exception ex) 585 { 586 // if we get there, something went awfully wrong while creatng one socket, 587 // no need to continue the for loop. 588 logger.traceException(ex); 589 throw new LDAPConnectionException(LocalizableMessage.raw(ex.getMessage()), ex); 590 } 591 if (ce != null) 592 { 593 LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 594 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, 595 ce); 596 } 597 return null; 598 } 599 600 /** 601 * Creates an SSL socket or a normal/basic socket using the hostName and 602 * portNumber encapsulated in the current object, or with the passed in socket 603 * if it needs to use start TLS. 604 * 605 * @param startTLSSocket 606 * the Socket to use if it needs to use start TLS. 607 * @param sslConnectionFactory 608 * the {@link SSLConnectionFactory} for creating SSL sockets 609 * @return a new {@link Socket} 610 * @throws SSLConnectionException 611 * if the SSL socket creation fails 612 * @throws LDAPConnectionException 613 * if any other error occurs 614 */ 615 private Socket createSSLOrBasicSocket(Socket startTLSSocket, 616 SSLConnectionFactory sslConnectionFactory) throws SSLConnectionException, 617 LDAPConnectionException 618 { 619 if (sslConnectionFactory == null) 620 { 621 return createSocket(); 622 } 623 else if (!connectionOptions.useStartTLS()) 624 { 625 return createSSLSocket(sslConnectionFactory); 626 } 627 else 628 { 629 try 630 { 631 // Use existing socket. 632 return sslConnectionFactory.createSocket(startTLSSocket, hostName, 633 portNumber, true); 634 } 635 catch (IOException e) 636 { 637 LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 638 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, 639 e); 640 } 641 } 642 } 643 644 /** 645 * Close the underlying ASN1 reader and writer, optionally sending an unbind 646 * request before disconnecting. 647 * 648 * @param nextMessageID The message ID counter that should be used for 649 * the unbind request, or {@code null} if the 650 * connection should be closed without an unbind 651 * request. 652 */ 653 public void close(AtomicInteger nextMessageID) 654 { 655 if(ldapWriter != null) 656 { 657 if (nextMessageID != null) 658 { 659 try 660 { 661 LDAPMessage message = new LDAPMessage(nextMessageID.getAndIncrement(), 662 new UnbindRequestProtocolOp()); 663 ldapWriter.writeMessage(message); 664 } catch (Exception e) {} 665 } 666 667 ldapWriter.close(); 668 } 669 if(ldapReader != null) 670 { 671 ldapReader.close(); 672 } 673 } 674 675 /** 676 * Get the underlying LDAP writer. 677 * 678 * @return The underlying LDAP writer. 679 */ 680 public LDAPWriter getLDAPWriter() 681 { 682 return ldapWriter; 683 } 684 685 /** 686 * Get the underlying LDAP reader. 687 * 688 * @return The underlying LDAP reader. 689 */ 690 public LDAPReader getLDAPReader() 691 { 692 return ldapReader; 693 } 694 695} 696