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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.tools;
028    import org.opends.messages.Message;
029    
030    import java.io.PrintStream;
031    import java.io.IOException;
032    import java.net.ConnectException;
033    import java.net.Socket;
034    import java.net.UnknownHostException;
035    import java.util.ArrayList;
036    import java.util.concurrent.atomic.AtomicInteger;
037    
038    import org.opends.server.controls.PasswordExpiringControl;
039    import org.opends.server.controls.PasswordPolicyErrorType;
040    import org.opends.server.controls.PasswordPolicyResponseControl;
041    import org.opends.server.controls.PasswordPolicyWarningType;
042    import org.opends.server.protocols.asn1.ASN1OctetString;
043    import org.opends.server.protocols.ldap.ExtendedRequestProtocolOp;
044    import org.opends.server.protocols.ldap.ExtendedResponseProtocolOp;
045    import org.opends.server.protocols.ldap.LDAPControl;
046    import org.opends.server.protocols.ldap.LDAPMessage;
047    import org.opends.server.protocols.ldap.UnbindRequestProtocolOp;
048    import org.opends.server.types.Control;
049    import org.opends.server.types.DebugLogLevel;
050    import org.opends.server.types.LDAPException;
051    
052    import static org.opends.server.loggers.debug.DebugLogger.*;
053    import org.opends.server.loggers.debug.DebugTracer;
054    import static org.opends.messages.CoreMessages.
055                       INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR;
056    import static org.opends.messages.ToolMessages.*;
057    import static org.opends.server.util.ServerConstants.*;
058    import static org.opends.server.util.StaticUtils.*;
059    import static org.opends.server.protocols.ldap.LDAPResultCode.*;
060    
061    
062    
063    /**
064     * This class provides a tool that can be used to issue search requests to the
065     * Directory Server.
066     */
067    public class LDAPConnection
068    {
069      /**
070       * The tracer object for the debug logger.
071       */
072      private static final DebugTracer TRACER = getTracer();
073    
074      // The hostname to connect to.
075      private String hostName = null;
076    
077      // The port number on which the directory server is accepting requests.
078      private int portNumber = 389;
079    
080      private LDAPConnectionOptions connectionOptions = null;
081      private LDAPWriter ldapWriter;
082      private LDAPReader ldapReader;
083      private int versionNumber = 3;
084    
085      private PrintStream out;
086      private 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        Socket socket;
156        Socket startTLSSocket = null;
157        int resultCode;
158        ArrayList<LDAPControl> requestControls = new ArrayList<LDAPControl> ();
159        ArrayList<LDAPControl> responseControls = new ArrayList<LDAPControl> ();
160    
161        VerboseTracer tracer =
162             new VerboseTracer(connectionOptions.isVerbose(), err);
163        if(connectionOptions.useStartTLS())
164        {
165          try
166          {
167            startTLSSocket = new Socket(hostName, portNumber);
168            ldapWriter = new LDAPWriter(startTLSSocket, tracer);
169            ldapReader = new LDAPReader(startTLSSocket, tracer);
170          } catch(UnknownHostException uhe)
171          {
172            Message msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get();
173            throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null,
174                                              uhe);
175          } catch(ConnectException ce)
176          {
177            Message msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get();
178            throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null,
179                                              ce);
180          } catch(Exception ex)
181          {
182            if (debugEnabled())
183            {
184              TRACER.debugCaught(DebugLogLevel.ERROR, ex);
185            }
186            throw new LDAPConnectionException(Message.raw(ex.getMessage()), ex);
187          }
188    
189          // Send the StartTLS extended request.
190          ExtendedRequestProtocolOp extendedRequest =
191               new ExtendedRequestProtocolOp(OID_START_TLS_REQUEST);
192    
193          LDAPMessage msg = new LDAPMessage(nextMessageID.getAndIncrement(),
194                                            extendedRequest);
195          try
196          {
197            ldapWriter.writeMessage(msg);
198    
199            // Read the response from the server.
200            msg = ldapReader.readMessage();
201          } catch (Exception ex1)
202          {
203            if (debugEnabled())
204            {
205              TRACER.debugCaught(DebugLogLevel.ERROR, ex1);
206            }
207            throw new LDAPConnectionException(Message.raw(ex1.getMessage()), ex1);
208          }
209          ExtendedResponseProtocolOp res = msg.getExtendedResponseProtocolOp();
210          resultCode = res.getResultCode();
211          if(resultCode != SUCCESS)
212          {
213            throw new LDAPConnectionException(res.getErrorMessage(),
214                                              resultCode,
215                                              res.getErrorMessage(),
216                                              res.getMatchedDN(), null);
217          }
218        }
219        SSLConnectionFactory sslConnectionFactory =
220                             connectionOptions.getSSLConnectionFactory();
221        try
222        {
223          if(sslConnectionFactory != null)
224          {
225            if(connectionOptions.useStartTLS())
226            {
227              // Use existing socket.
228              socket = sslConnectionFactory.createSocket(startTLSSocket, hostName,
229                portNumber, true);
230            } else
231            {
232              socket = sslConnectionFactory.createSocket(hostName, portNumber);
233            }
234          } else
235          {
236            socket = new Socket(hostName, portNumber);
237          }
238          ldapWriter = new LDAPWriter(socket, tracer);
239          ldapReader = new LDAPReader(socket, tracer);
240        } catch(UnknownHostException uhe)
241        {
242          Message msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get();
243          throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null,
244                                            uhe);
245        } catch(ConnectException ce)
246        {
247          Message msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get();
248          throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null,
249                                            ce);
250        } catch(Exception ex2)
251        {
252          if (debugEnabled())
253          {
254            TRACER.debugCaught(DebugLogLevel.ERROR, ex2);
255          }
256          throw new LDAPConnectionException(Message.raw(ex2.getMessage()), ex2);
257        }
258    
259        // We need this so that we don't run out of addresses when the tool
260        // commands are called A LOT, as in the unit tests.
261        try
262        {
263          socket.setSoLinger(true, 1);
264          socket.setReuseAddress(true);
265        } catch(IOException e)
266        {
267          if (debugEnabled())
268          {
269            TRACER.debugCaught(DebugLogLevel.ERROR, e);
270          }
271          // It doesn't matter too much if this throws, so ignore it.
272        }
273    
274        if (connectionOptions.getReportAuthzID())
275        {
276          requestControls.add(new LDAPControl(OID_AUTHZID_REQUEST));
277        }
278    
279        if (connectionOptions.usePasswordPolicyControl())
280        {
281          requestControls.add(new LDAPControl(OID_PASSWORD_POLICY_CONTROL));
282        }
283    
284        LDAPAuthenticationHandler handler = new LDAPAuthenticationHandler(
285             ldapReader, ldapWriter, hostName, nextMessageID);
286        try
287        {
288          ASN1OctetString bindPW;
289          if (bindPassword == null)
290          {
291            bindPW = null;
292          }
293          else
294          {
295            bindPW = new ASN1OctetString(bindPassword);
296          }
297    
298          String result = null;
299          if (connectionOptions.useSASLExternal())
300          {
301            result = handler.doSASLExternal(new ASN1OctetString(bindDN),
302                                            connectionOptions.getSASLProperties(),
303                                            requestControls, responseControls);
304          }
305          else if (connectionOptions.getSASLMechanism() != null)
306          {
307                result = handler.doSASLBind(new ASN1OctetString(bindDN), bindPW,
308                connectionOptions.getSASLMechanism(),
309                connectionOptions.getSASLProperties(),
310                requestControls, responseControls);
311          }
312          else if(bindDN != null)
313          {
314                  result = handler.doSimpleBind(versionNumber,
315                      new ASN1OctetString(bindDN), bindPW,
316                  requestControls, responseControls);
317          }
318          if(result != null)
319          {
320            out.println(result);
321          }
322    
323          for (LDAPControl c : responseControls)
324          {
325            if (c.getOID().equals(OID_AUTHZID_RESPONSE))
326            {
327              ASN1OctetString controlValue = c.getValue();
328              if (controlValue != null)
329              {
330    
331                Message message =
332                        INFO_BIND_AUTHZID_RETURNED.get(controlValue.stringValue());
333                out.println(message);
334              }
335            }
336            else if (c.getOID().equals(OID_NS_PASSWORD_EXPIRED))
337            {
338    
339              Message message = INFO_BIND_PASSWORD_EXPIRED.get();
340              out.println(message);
341            }
342            else if (c.getOID().equals(OID_NS_PASSWORD_EXPIRING))
343            {
344              PasswordExpiringControl expiringControl =
345                   PasswordExpiringControl.decodeControl(new Control(c.getOID(),
346                                                                     c.isCritical(),
347                                                                     c.getValue()));
348              Message timeString =
349                   secondsToTimeString(expiringControl.getSecondsUntilExpiration());
350    
351    
352              Message message = INFO_BIND_PASSWORD_EXPIRING.get(timeString);
353              out.println(message);
354            }
355            else if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL))
356            {
357              PasswordPolicyResponseControl pwPolicyControl =
358                   PasswordPolicyResponseControl.decodeControl(new Control(
359                        c.getOID(), c.isCritical(), c.getValue()));
360    
361              PasswordPolicyErrorType errorType = pwPolicyControl.getErrorType();
362              if (errorType != null)
363              {
364                switch (errorType)
365                {
366                  case PASSWORD_EXPIRED:
367    
368                    Message message = INFO_BIND_PASSWORD_EXPIRED.get();
369                    out.println(message);
370                    break;
371                  case ACCOUNT_LOCKED:
372    
373                    message = INFO_BIND_ACCOUNT_LOCKED.get();
374                    out.println(message);
375                    break;
376                  case CHANGE_AFTER_RESET:
377    
378                    message = INFO_BIND_MUST_CHANGE_PASSWORD.get();
379                    out.println(message);
380                    break;
381                }
382              }
383    
384              PasswordPolicyWarningType warningType =
385                   pwPolicyControl.getWarningType();
386              if (warningType != null)
387              {
388                switch (warningType)
389                {
390                  case TIME_BEFORE_EXPIRATION:
391                    Message timeString =
392                         secondsToTimeString(pwPolicyControl.getWarningValue());
393    
394    
395                    Message message = INFO_BIND_PASSWORD_EXPIRING.get(timeString);
396                    out.println(message);
397                    break;
398                  case GRACE_LOGINS_REMAINING:
399    
400                    message = INFO_BIND_GRACE_LOGINS_REMAINING.get(
401                            pwPolicyControl.getWarningValue());
402                    out.println(message);
403                    break;
404                }
405              }
406            }
407          }
408        } catch(ClientException ce)
409        {
410          if (debugEnabled())
411          {
412            TRACER.debugCaught(DebugLogLevel.ERROR, ce);
413          }
414          throw new LDAPConnectionException(ce.getMessageObject(), ce.getExitCode(),
415                                            null, ce);
416        } catch (LDAPException le)
417        {
418          throw new LDAPConnectionException(le.getMessageObject(),
419                                            le.getResultCode(),
420                                            le.getErrorMessage(),
421                                            le.getMatchedDN(),
422                                            le.getCause());
423        } catch(Exception ex)
424        {
425          if (debugEnabled())
426          {
427            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
428          }
429          throw new LDAPConnectionException(
430                  Message.raw(ex.getLocalizedMessage()),ex);
431        }
432    
433      }
434    
435      /**
436       * Close the underlying ASN1 reader and writer, optionally sending an unbind
437       * request before disconnecting.
438       *
439       * @param  nextMessageID  The message ID counter that should be used for
440       *                        the unbind request, or {@code null} if the
441       *                        connection should be closed without an unbind
442       *                        request.
443       */
444      public void close(AtomicInteger nextMessageID)
445      {
446        if(ldapWriter != null)
447        {
448          if (nextMessageID != null)
449          {
450            try
451            {
452              LDAPMessage message = new LDAPMessage(nextMessageID.getAndIncrement(),
453                                                    new UnbindRequestProtocolOp());
454              ldapWriter.writeMessage(message);
455            } catch (Exception e) {}
456          }
457    
458          ldapWriter.close();
459        }
460        if(ldapReader != null)
461        {
462          ldapReader.close();
463        }
464      }
465    
466      /**
467       * Get the underlying LDAP writer.
468       *
469       * @return  The underlying LDAP writer.
470       */
471      public LDAPWriter getLDAPWriter()
472      {
473        return ldapWriter;
474      }
475    
476      /**
477       * Get the underlying LDAP reader.
478       *
479       * @return  The underlying LDAP reader.
480       */
481      public LDAPReader getLDAPReader()
482      {
483        return ldapReader;
484      }
485    
486    }
487