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.ArgumentConstants.*;
020import static com.forgerock.opendj.cli.CliMessages.INFO_BINDPWD_FILE_PLACEHOLDER;
021import static com.forgerock.opendj.cli.CliMessages.INFO_KEYSTORE_PWD_FILE_PLACEHOLDER;
022import static com.forgerock.opendj.cli.CliMessages.INFO_PORT_PLACEHOLDER;
023import static com.forgerock.opendj.cli.CliMessages.INFO_TRUSTSTORE_PWD_FILE_PLACEHOLDER;
024import static com.forgerock.opendj.cli.Utils.*;
025import static com.forgerock.opendj.cli.CommonArguments.*;
026
027import static org.opends.messages.ToolMessages.*;
028import static org.opends.server.config.ConfigConstants.*;
029import static org.opends.server.util.CollectionUtils.*;
030import static org.opends.server.protocols.ldap.LDAPResultCode.*;
031import static org.opends.server.util.ServerConstants.*;
032import static org.opends.server.util.StaticUtils.*;
033
034import java.io.IOException;
035import java.io.OutputStream;
036import java.io.PrintStream;
037import java.text.SimpleDateFormat;
038import java.util.ArrayList;
039import java.util.Date;
040import java.util.LinkedList;
041import java.util.TimeZone;
042import java.util.UUID;
043import java.util.concurrent.atomic.AtomicInteger;
044
045import javax.net.ssl.SSLException;
046
047import org.forgerock.i18n.LocalizableMessage;
048import org.forgerock.opendj.ldap.ByteString;
049import org.forgerock.opendj.ldap.DecodeException;
050import org.opends.server.config.AdministrationConnector;
051import org.opends.server.controls.ProxiedAuthV2Control;
052import org.opends.server.core.DirectoryServer;
053import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler;
054import org.opends.server.core.LockFileManager;
055import org.opends.server.loggers.JDKLogging;
056import org.opends.server.protocols.ldap.AddRequestProtocolOp;
057import org.opends.server.protocols.ldap.AddResponseProtocolOp;
058import org.opends.server.protocols.ldap.ExtendedResponseProtocolOp;
059import org.opends.server.protocols.ldap.LDAPAttribute;
060import org.opends.server.protocols.ldap.LDAPConstants;
061import org.opends.server.protocols.ldap.LDAPMessage;
062import org.opends.server.protocols.ldap.LDAPResultCode;
063import org.opends.server.tasks.ShutdownTask;
064import org.opends.server.tools.tasks.TaskTool;
065import org.opends.server.types.Control;
066import org.opends.server.types.LDAPException;
067import org.opends.server.types.NullOutputStream;
068import org.opends.server.types.RawAttribute;
069import org.opends.server.util.cli.LDAPConnectionArgumentParser;
070
071import com.forgerock.opendj.cli.Argument;
072import com.forgerock.opendj.cli.ArgumentConstants;
073import com.forgerock.opendj.cli.ArgumentException;
074import com.forgerock.opendj.cli.ArgumentParser;
075import com.forgerock.opendj.cli.BooleanArgument;
076import com.forgerock.opendj.cli.FileBasedArgument;
077import com.forgerock.opendj.cli.IntegerArgument;
078import com.forgerock.opendj.cli.StringArgument;
079
080/**
081 * This class provides a tool that can send a request to the Directory Server
082 * that will cause it to shut down.
083 */
084public class StopDS
085{
086  /** The fully-qualified name of this class. */
087  private static final String CLASS_NAME = "org.opends.server.tools.StopDS";
088
089  /**
090   * Return codes used when the hidden option --checkStoppability is used.
091   * NOTE: when checkStoppability is specified is recommended not to allocate
092   * a lot of memory for the JVM (Using -Xms and -Xmx options) as there might
093   * be calls to Runtime.exec.
094   */
095  /** The server is already stopped. */
096  private static int SERVER_ALREADY_STOPPED = 98;
097  /** The server must be started. */
098  private static int START_SERVER = 99;
099  /** The server must be stopped using a system call. */
100  private static int STOP_USING_SYSTEM_CALL = 100;
101  /** The server must be restarted using system calls. */
102  private static int RESTART_USING_SYSTEM_CALL = 101;
103  /** The server must be stopped using protocol. */
104  private static int STOP_USING_PROTOCOL = 102;
105  /** The server must be stopped as a window service. */
106  private static int STOP_AS_WINDOW_SERVICE = 103;
107  /** The server must be restarted as a window service. */
108  private static int RESTART_AS_WINDOW_SERVICE = 104;
109  /** The server must be started and it should use quiet mode. */
110  private static int START_SERVER_QUIET = 105;
111  /** The server must be restarted using system calls and it should use quiet mode. */
112  private static int RESTART_USING_SYSTEM_CALL_QUIET = 106;
113
114  /**
115   * Invokes the <CODE>stopDS</CODE> method, passing it the provided command
116   * line arguments.  If the call to <CODE>stopDS</CODE> returns a nonzero
117   * value, then that will be used as the exit code for this program.
118   *
119   * @param  args  The command-line arguments provided to this program.
120   */
121  public static void main(String[] args)
122  {
123    int result = stopDS(args, System.out, System.err);
124
125    if (result != LDAPResultCode.SUCCESS)
126    {
127      System.exit(filterExitCode(result));
128    }
129  }
130
131
132
133  /**
134   * Parses the provided set of command-line arguments and attempts to contact
135   * the Directory Server in order to send it the shutdown request.
136   *
137   * @param  args  The command-line arguments provided to this program.
138   *
139   * @return  An integer value that indicates whether the shutdown request was
140   *          accepted by the Directory Server.  A nonzero value should be
141   *          interpreted as a failure of some kind.
142   */
143  public static int stopDS(String[] args)
144  {
145    return stopDS(args, System.out, System.err);
146  }
147
148
149
150  /**
151   * Parses the provided set of command-line arguments and attempts to contact
152   * the Directory Server in order to send it the shutdown request.
153   *
154   * @param  args       The command-line arguments provided to this program.
155   * @param  outStream  The output stream to use for standard output, or
156   *                    <CODE>null</CODE> if standard output is not needed.
157   * @param  errStream  The output stream to use for standard error, or
158   *                    <CODE>null</CODE> if standard error is not needed.
159   *
160   * @return  An integer value that indicates whether the shutdown request was
161   *          accepted by the Directory Server.  A nonzero value should be
162   *          interpreted as a failure of some kind.
163   */
164  public static int stopDS(String[] args, OutputStream outStream,
165                           OutputStream errStream)
166  {
167    PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
168    PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
169    JDKLogging.disableLogging();
170
171    // Define all the arguments that may be used with this program.
172    LocalizableMessage toolDescription = INFO_STOPDS_TOOL_DESCRIPTION.get();
173    ArgumentParser    argParser = new ArgumentParser(CLASS_NAME,
174                                                     toolDescription, false);
175    argParser.setShortToolDescription(REF_SHORT_DESC_STOP_DS.get());
176
177    argParser.setVersionHandler(new DirectoryServerVersionHandler());
178    BooleanArgument   checkStoppability;
179    BooleanArgument   quietMode;
180    BooleanArgument   restart;
181    BooleanArgument   showUsage;
182    BooleanArgument   trustAll;
183    FileBasedArgument bindPWFile;
184    FileBasedArgument keyStorePWFile;
185    FileBasedArgument trustStorePWFile;
186    IntegerArgument   port;
187    StringArgument    bindDN;
188    StringArgument    bindPW;
189    StringArgument    certNickname;
190    StringArgument    host;
191    StringArgument    keyStoreFile;
192    StringArgument    keyStorePW;
193    StringArgument    proxyAuthzID;
194    StringArgument    saslOption;
195    StringArgument    stopReason;
196    StringArgument    stopTimeStr;
197    StringArgument    trustStoreFile;
198    StringArgument    trustStorePW;
199    StringArgument    propertiesFileArgument;
200    BooleanArgument   noPropertiesFileArgument;
201
202    try
203    {
204      propertiesFileArgument =
205              StringArgument.builder(OPTION_LONG_PROP_FILE_PATH)
206                      .description(INFO_DESCRIPTION_PROP_FILE_PATH.get())
207                      .valuePlaceholder(INFO_PROP_FILE_PATH_PLACEHOLDER.get())
208                      .buildAndAddToParser(argParser);
209      argParser.setFilePropertiesArgument(propertiesFileArgument);
210
211      noPropertiesFileArgument =
212              BooleanArgument.builder(OPTION_LONG_NO_PROP_FILE)
213                      .description(INFO_DESCRIPTION_NO_PROP_FILE.get())
214                      .buildAndAddToParser(argParser);
215      argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
216
217      host =
218              StringArgument.builder(OPTION_LONG_HOST)
219                      .shortIdentifier(OPTION_SHORT_HOST)
220                      .description(INFO_STOPDS_DESCRIPTION_HOST.get())
221                      .defaultValue("127.0.0.1")
222                      .valuePlaceholder(INFO_HOST_PLACEHOLDER.get())
223                      .buildAndAddToParser(argParser);
224      port =
225              IntegerArgument.builder(OPTION_LONG_PORT)
226                      .shortIdentifier(OPTION_SHORT_PORT)
227                      .description(INFO_STOPDS_DESCRIPTION_PORT.get())
228                      .range(1, 65535)
229                      .defaultValue(AdministrationConnector.DEFAULT_ADMINISTRATION_CONNECTOR_PORT)
230                      .valuePlaceholder(INFO_PORT_PLACEHOLDER.get())
231                      .buildAndAddToParser(argParser);
232      bindDN =
233              StringArgument.builder(OPTION_LONG_BINDDN)
234                      .shortIdentifier(OPTION_SHORT_BINDDN)
235                      .description(INFO_STOPDS_DESCRIPTION_BINDDN.get())
236                      .valuePlaceholder(INFO_BINDDN_PLACEHOLDER.get())
237                      .buildAndAddToParser(argParser);
238      bindPW =
239              StringArgument.builder(OPTION_LONG_BINDPWD)
240                      .shortIdentifier(OPTION_SHORT_BINDPWD)
241                      .description(INFO_STOPDS_DESCRIPTION_BINDPW.get())
242                      .valuePlaceholder(INFO_BINDPWD_PLACEHOLDER.get())
243                      .buildAndAddToParser(argParser);
244      bindPWFile =
245              FileBasedArgument.builder(OPTION_LONG_BINDPWD_FILE)
246                      .shortIdentifier(OPTION_SHORT_BINDPWD_FILE)
247                      .description(INFO_STOPDS_DESCRIPTION_BINDPWFILE.get())
248                      .valuePlaceholder(INFO_BINDPWD_FILE_PLACEHOLDER.get())
249                      .buildAndAddToParser(argParser);
250      saslOption =
251              StringArgument.builder(OPTION_LONG_SASLOPTION)
252                      .shortIdentifier(OPTION_SHORT_SASLOPTION)
253                      .description(INFO_STOPDS_DESCRIPTION_SASLOPTIONS.get())
254                      .multiValued()
255                      .valuePlaceholder(INFO_SASL_OPTION_PLACEHOLDER.get())
256                      .buildAndAddToParser(argParser);
257      proxyAuthzID =
258              StringArgument.builder(OPTION_LONG_PROXYAUTHID)
259                      .shortIdentifier(OPTION_SHORT_PROXYAUTHID)
260                      .description(INFO_STOPDS_DESCRIPTION_PROXYAUTHZID.get())
261                      .valuePlaceholder(INFO_PROXYAUTHID_PLACEHOLDER.get())
262                      .buildAndAddToParser(argParser);
263      stopReason =
264              StringArgument.builder("stopReason")
265                      .shortIdentifier('r')
266                      .description(INFO_STOPDS_DESCRIPTION_STOP_REASON.get())
267                      .valuePlaceholder(INFO_STOP_REASON_PLACEHOLDER.get())
268                      .buildAndAddToParser(argParser);
269      checkStoppability =
270              BooleanArgument.builder(OPTION_LONG_CHECK_STOPPABILITY)
271                      .description(INFO_STOPDS_CHECK_STOPPABILITY.get())
272                      .hidden()
273                      .buildAndAddToParser(argParser);
274      BooleanArgument.builder(OPTION_LONG_WINDOWS_NET_STOP)
275              .description(INFO_STOPDS_DESCRIPTION_WINDOWS_NET_STOP.get())
276              .hidden()
277              .buildAndAddToParser(argParser);
278
279      restart = restartArgument();
280      argParser.addArgument(restart);
281
282      stopTimeStr =
283              StringArgument.builder("stopTime")
284                      .shortIdentifier('t')
285                      .description(INFO_STOPDS_DESCRIPTION_STOP_TIME.get())
286                      .valuePlaceholder(INFO_STOP_TIME_PLACEHOLDER.get())
287                      .buildAndAddToParser(argParser);
288
289      trustAll = trustAllArgument();
290      argParser.addArgument(trustAll);
291
292      keyStoreFile =
293              StringArgument.builder(OPTION_LONG_KEYSTOREPATH)
294                      .shortIdentifier(OPTION_SHORT_KEYSTOREPATH)
295                      .description(INFO_STOPDS_DESCRIPTION_KSFILE.get())
296                      .valuePlaceholder(INFO_KEYSTOREPATH_PLACEHOLDER.get())
297                      .buildAndAddToParser(argParser);
298      keyStorePW =
299              StringArgument.builder(OPTION_LONG_KEYSTORE_PWD)
300                      .shortIdentifier(OPTION_SHORT_KEYSTORE_PWD)
301                      .description(INFO_STOPDS_DESCRIPTION_KSPW.get())
302                      .valuePlaceholder(INFO_KEYSTORE_PWD_PLACEHOLDER.get())
303                      .buildAndAddToParser(argParser);
304      keyStorePWFile =
305              FileBasedArgument.builder(OPTION_LONG_KEYSTORE_PWD_FILE)
306                      .shortIdentifier(OPTION_SHORT_KEYSTORE_PWD_FILE)
307                      .description(INFO_STOPDS_DESCRIPTION_KSPWFILE.get())
308                      .valuePlaceholder(INFO_KEYSTORE_PWD_FILE_PLACEHOLDER.get())
309                      .buildAndAddToParser(argParser);
310      certNickname =
311              StringArgument.builder("certNickname")
312                      .shortIdentifier('N')
313                      .description(INFO_DESCRIPTION_CERT_NICKNAME.get())
314                      .valuePlaceholder(INFO_NICKNAME_PLACEHOLDER.get())
315                      .buildAndAddToParser(argParser);
316      trustStoreFile =
317              StringArgument.builder(OPTION_LONG_TRUSTSTOREPATH)
318                      .shortIdentifier(OPTION_SHORT_TRUSTSTOREPATH)
319                      .description(INFO_STOPDS_DESCRIPTION_TSFILE.get())
320                      .valuePlaceholder(INFO_TRUSTSTOREPATH_PLACEHOLDER.get())
321                      .buildAndAddToParser(argParser);
322      trustStorePW =
323              StringArgument.builder(OPTION_LONG_TRUSTSTORE_PWD)
324                      .shortIdentifier('T')
325                      .description(INFO_STOPDS_DESCRIPTION_TSPW.get())
326                      .valuePlaceholder(INFO_TRUSTSTORE_PWD_PLACEHOLDER.get())
327                      .buildAndAddToParser(argParser);
328      trustStorePWFile =
329              FileBasedArgument.builder(OPTION_LONG_TRUSTSTORE_PWD_FILE)
330                      .shortIdentifier(OPTION_SHORT_TRUSTSTORE_PWD_FILE)
331                      .description(INFO_STOPDS_DESCRIPTION_TSPWFILE.get())
332                      .valuePlaceholder(INFO_TRUSTSTORE_PWD_FILE_PLACEHOLDER.get())
333                      .buildAndAddToParser(argParser);
334
335      quietMode = quietArgument();
336      argParser.addArgument(quietMode);
337
338      showUsage = showUsageArgument();
339      argParser.addArgument(showUsage);
340      argParser.setUsageArgument(showUsage, out);
341    }
342    catch (ArgumentException ae)
343    {
344      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
345      return CLIENT_SIDE_PARAM_ERROR;
346    }
347
348
349    // Parse the command-line arguments provided to the program.
350    try
351    {
352      argParser.parseArguments(args);
353    }
354    catch (ArgumentException ae)
355    {
356      argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
357      return CLIENT_SIDE_PARAM_ERROR;
358    }
359
360
361    // If we should just display usage or version information,
362    // then exit because it will have already been done.
363    if (argParser.usageOrVersionDisplayed())
364    {
365      return LDAPResultCode.SUCCESS;
366    }
367
368    if (quietMode.isPresent())
369    {
370      out = NullOutputStream.printStream();
371    }
372
373    if (checkStoppability.isPresent())
374    {
375      System.exit(checkStoppability(argParser, out, err));
376    }
377
378    // If both a bind password and bind password file were provided, then return
379    // an error.
380    if (bindPW.isPresent() && bindPWFile.isPresent())
381    {
382      printWrappedText(err,
383          ERR_STOPDS_MUTUALLY_EXCLUSIVE_ARGUMENTS.get(bindPW.getLongIdentifier(), bindPWFile.getLongIdentifier()));
384      return CLIENT_SIDE_PARAM_ERROR;
385    }
386
387
388    // If both a key store password and key store password file were provided,
389    // then return an error.
390    if (keyStorePW.isPresent() && keyStorePWFile.isPresent())
391    {
392      printWrappedText(err, ERR_STOPDS_MUTUALLY_EXCLUSIVE_ARGUMENTS.get(
393              keyStorePW.getLongIdentifier(), keyStorePWFile.getLongIdentifier()));
394      return CLIENT_SIDE_PARAM_ERROR;
395    }
396
397
398    // If both a trust store password and trust store password file were
399    // provided, then return an error.
400    if (trustStorePW.isPresent() && trustStorePWFile.isPresent())
401    {
402      printWrappedText(err, ERR_STOPDS_MUTUALLY_EXCLUSIVE_ARGUMENTS.get(
403              trustStorePW.getLongIdentifier(), trustStorePWFile.getLongIdentifier()));
404      return CLIENT_SIDE_PARAM_ERROR;
405    }
406
407
408    // Make sure that we can decode the stop time string if one was provided.
409    Date stopTime = new Date();
410    if (stopTimeStr.isPresent())
411    {
412      String timeStr = stopTimeStr.getValue();
413      if (!TaskTool.NOW.equals(timeStr))
414      {
415        try
416        {
417          stopTime = parseDateTimeString(timeStr);
418        }
419        catch (Exception e)
420        {
421          printWrappedText(err, ERR_STOPDS_CANNOT_DECODE_STOP_TIME.get());
422          return CLIENT_SIDE_PARAM_ERROR;
423        }
424        // Check that the provided date is not previous to the current date.
425        Date currentDate = new Date(System.currentTimeMillis());
426        if (currentDate.after(stopTime))
427        {
428          printWrappedText(err, ERR_STOPDS_DATETIME_ALREADY_PASSED.get(timeStr));
429          return CLIENT_SIDE_PARAM_ERROR;
430        }
431      }
432    }
433
434
435    // Create the LDAP connection options object, which will be used to
436    // customize the way that we connect to the server and specify a set of
437    // basic defaults.
438    LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions();
439    connectionOptions.setVersionNumber(3);
440
441
442    try {
443      String clientAlias;
444      if (certNickname.isPresent()) {
445        clientAlias = certNickname.getValue();
446      } else {
447        clientAlias = null;
448      }
449
450      SSLConnectionFactory sslConnectionFactory = new SSLConnectionFactory();
451      sslConnectionFactory.init(trustAll.isPresent(), keyStoreFile.getValue(),
452        keyStorePW.getValue(), clientAlias,
453        trustStoreFile.getValue(),
454        trustStorePW.getValue());
455
456      connectionOptions.setSSLConnectionFactory(sslConnectionFactory);
457    } catch (SSLConnectionException sce) {
458      printWrappedText(err, ERR_STOPDS_CANNOT_INITIALIZE_SSL.get(sce.getMessage()));
459      return CLIENT_SIDE_LOCAL_ERROR;
460    }
461
462
463    // If one or more SASL options were provided, then make sure that one of
464    // them was "mech" and specified a valid SASL mechanism.
465    if (saslOption.isPresent())
466    {
467      String             mechanism = null;
468      LinkedList<String> options   = new LinkedList<>();
469
470      for (String s : saslOption.getValues())
471      {
472        int equalPos = s.indexOf('=');
473        if (equalPos <= 0)
474        {
475          printWrappedText(err, ERR_STOPDS_CANNOT_PARSE_SASL_OPTION.get(s));
476          return CLIENT_SIDE_PARAM_ERROR;
477        }
478        else
479        {
480          String name  = s.substring(0, equalPos);
481
482          if (name.equalsIgnoreCase("mech"))
483          {
484            mechanism = s;
485          }
486          else
487          {
488            options.add(s);
489          }
490        }
491      }
492
493      if (mechanism == null)
494      {
495        printWrappedText(err, ERR_STOPDS_NO_SASL_MECHANISM.get());
496        return CLIENT_SIDE_PARAM_ERROR;
497      }
498
499      connectionOptions.setSASLMechanism(mechanism);
500
501      for (String option : options)
502      {
503        connectionOptions.addSASLProperty(option);
504      }
505    }
506
507
508    // Attempt to connect and authenticate to the Directory Server.
509    AtomicInteger nextMessageID = new AtomicInteger(1);
510    LDAPConnection connection;
511    try
512    {
513      connection = new LDAPConnection(host.getValue(), port.getIntValue(),
514                                      connectionOptions, out, err);
515      connection.connectToHost(bindDN.getValue(),
516          LDAPConnectionArgumentParser.getPasswordValue(bindPW, bindPWFile,
517                                                        bindDN, out, err),
518          nextMessageID);
519    }
520    catch (ArgumentException ae)
521    {
522      argParser.displayMessageAndUsageReference(
523          err, ERR_STOPDS_CANNOT_DETERMINE_PORT.get(port.getLongIdentifier(), ae.getMessage()));
524      return CLIENT_SIDE_PARAM_ERROR;
525    }
526    catch (LDAPConnectionException lce)
527    {
528      LocalizableMessage message = null;
529      if (lce.getCause() != null && lce.getCause().getCause() != null &&
530        lce.getCause().getCause() instanceof SSLException) {
531      message = ERR_STOPDS_CANNOT_CONNECT_SSL.get(host.getValue(),
532        port.getValue());
533      } else {
534        String hostPort = host.getValue() + ":" + port.getValue();
535        message = ERR_STOPDS_CANNOT_CONNECT.get(hostPort,
536          lce.getMessage());
537      }
538      printWrappedText(err, message);
539      return CLIENT_SIDE_CONNECT_ERROR;
540    }
541
542    LDAPReader reader = connection.getLDAPReader();
543    LDAPWriter writer = connection.getLDAPWriter();
544
545
546    // Construct the add request to send to the server.
547    String taskID = UUID.randomUUID().toString();
548    ByteString entryDN =
549        ByteString.valueOfUtf8(ATTR_TASK_ID + "=" + taskID + "," +
550                            SCHEDULED_TASK_BASE_RDN + "," + DN_TASK_ROOT);
551
552    ArrayList<RawAttribute> attributes = new ArrayList<>();
553    attributes.add(new LDAPAttribute(ATTR_OBJECTCLASS, newArrayList("top", "ds-task", "ds-task-shutdown")));
554    attributes.add(new LDAPAttribute(ATTR_TASK_ID, taskID));
555    attributes.add(new LDAPAttribute(ATTR_TASK_CLASS, ShutdownTask.class.getName()));
556    if (restart.isPresent())
557    {
558      attributes.add(new LDAPAttribute(ATTR_RESTART_SERVER, "true"));
559    }
560    if (stopReason.isPresent())
561    {
562      attributes.add(new LDAPAttribute(ATTR_SHUTDOWN_MESSAGE, stopReason.getValue()));
563    }
564
565    if (stopTime != null)
566    {
567      SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
568      dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
569      String stopTimeValues = dateFormat.format(stopTime);
570      attributes.add(new LDAPAttribute(ATTR_TASK_SCHEDULED_START_TIME, stopTimeValues));
571    }
572
573    ArrayList<Control> controls = new ArrayList<>();
574    if (proxyAuthzID.isPresent())
575    {
576      controls.add(new ProxiedAuthV2Control(
577          ByteString.valueOfUtf8(proxyAuthzID.getValue())));
578    }
579
580    AddRequestProtocolOp addRequest = new AddRequestProtocolOp(entryDN, attributes);
581    LDAPMessage requestMessage =
582         new LDAPMessage(nextMessageID.getAndIncrement(), addRequest, controls);
583
584
585    // Send the request to the server and read the response.
586    LDAPMessage responseMessage;
587    try
588    {
589      writer.writeMessage(requestMessage);
590
591      responseMessage = reader.readMessage();
592      if (responseMessage == null)
593      {
594        printWrappedText(err, ERR_STOPDS_UNEXPECTED_CONNECTION_CLOSURE.get());
595        return CLIENT_SIDE_SERVER_DOWN;
596      }
597    }
598    catch (DecodeException | LDAPException e)
599    {
600      printWrappedText(err, ERR_STOPDS_DECODE_ERROR.get(e.getMessage()));
601      return CLIENT_SIDE_DECODING_ERROR;
602    }
603    catch (IOException ioe)
604    {
605      printWrappedText(err, ERR_STOPDS_IO_ERROR.get(ioe));
606      return LDAPResultCode.CLIENT_SIDE_SERVER_DOWN;
607    }
608
609
610    if (responseMessage.getProtocolOpType() !=
611        LDAPConstants.OP_TYPE_ADD_RESPONSE)
612    {
613      if (responseMessage.getProtocolOpType() ==
614          LDAPConstants.OP_TYPE_EXTENDED_RESPONSE)
615      {
616        // It's possible that this is a notice of disconnection, which we can
617        // probably interpret as a "success" in this case.
618        ExtendedResponseProtocolOp extendedResponse =
619             responseMessage.getExtendedResponseProtocolOp();
620        String responseOID = extendedResponse.getOID();
621        if (LDAPConstants.OID_NOTICE_OF_DISCONNECTION.equals(responseOID))
622        {
623          printWrappedText(err, extendedResponse.getErrorMessage());
624          return extendedResponse.getResultCode();
625        }
626      }
627
628
629      printWrappedText(err, ERR_STOPDS_INVALID_RESPONSE_TYPE.get(responseMessage.getProtocolOpName()));
630
631
632      return CLIENT_SIDE_LOCAL_ERROR;
633    }
634
635
636    AddResponseProtocolOp addResponse = responseMessage.getAddResponseProtocolOp();
637    printWrappedText(err, addResponse.getErrorMessage());
638    return addResponse.getResultCode();
639  }
640
641  /**
642   * Returns the error code that we return when we are checking the stoppability
643   * of the server.  This basically tells the invoker what must be done based
644   * on the different parameters passed.
645   * @param argParser the ArgumentParser with the arguments already parsed.
646   * @param out the print stream to use for standard output.
647   * @param err the print stream to use for standard error.
648   * @return the error code that we return when we are checking the stoppability
649   * of the server.
650   */
651  private static int checkStoppability(ArgumentParser argParser,
652                                       PrintStream out, PrintStream err)
653  {
654    int returnValue;
655    boolean isServerRunning;
656
657    boolean quietMode = false;
658    Argument quietArg = argParser.getArgumentForLongID(ArgumentConstants.OPTION_LONG_QUIET);
659    if (quietArg != null && quietArg.isPresent())
660    {
661      quietMode = true;
662    }
663
664    final boolean restartPresent = argParser.getArgumentForLongID(OPTION_LONG_RESTART).isPresent();
665    final boolean windowsNetStopPresent = argParser.getArgumentForLongID(OPTION_LONG_WINDOWS_NET_STOP).isPresent();
666
667    // Check if this is a stop through protocol.
668    boolean stopThroughProtocol = false;
669    for (final Argument arg: argParser.getArgumentList())
670    {
671      if (!OPTION_LONG_RESTART.equals(arg.getLongIdentifier()) &&
672          !OPTION_LONG_QUIET.equals(arg.getLongIdentifier()) &&
673          !OPTION_LONG_HELP.equals(arg.getLongIdentifier()) &&
674          !OPTION_LONG_CHECK_STOPPABILITY.equals(arg.getLongIdentifier()) &&
675          !OPTION_LONG_WINDOWS_NET_STOP.equals(arg.getLongIdentifier()) &&
676          !OPTION_LONG_NO_PROP_FILE.equals(arg.getLongIdentifier()))
677      {
678        stopThroughProtocol |= arg.isPresent();
679      }
680    }
681
682    if (stopThroughProtocol)
683    {
684      // Assume that this is done on a remote server and do no more checks.
685      returnValue = STOP_USING_PROTOCOL;
686    }
687    else
688    {
689      String lockFile = LockFileManager.getServerLockFileName();
690      try
691      {
692        StringBuilder failureReason = new StringBuilder();
693        if (LockFileManager.acquireExclusiveLock(lockFile, failureReason))
694        {
695          // The server is not running: write a message informing of that
696          // in the standard out (this is not an error message).
697          LocalizableMessage message = INFO_STOPDS_SERVER_ALREADY_STOPPED.get();
698          out.println(message);
699          LockFileManager.releaseLock(lockFile, failureReason);
700          isServerRunning = false;
701        }
702        else
703        {
704          isServerRunning = true;
705        }
706      }
707      catch (Exception e)
708      {
709        // Assume that if we cannot acquire the lock file the server is
710        // running.
711        isServerRunning = true;
712      }
713
714      boolean configuredAsService =
715          DirectoryServer.isRunningAsWindowsService();
716
717      if (!isServerRunning)
718      {
719        if (configuredAsService && !windowsNetStopPresent)
720        {
721          if (restartPresent)
722          {
723            returnValue = RESTART_AS_WINDOW_SERVICE;
724          }
725          else
726          {
727            returnValue = STOP_AS_WINDOW_SERVICE;
728          }
729        }
730        else if (restartPresent)
731        {
732          if (quietMode)
733          {
734            returnValue = START_SERVER_QUIET;
735          }
736          else
737          {
738            returnValue = START_SERVER;
739          }
740        }
741        else
742        {
743          returnValue = SERVER_ALREADY_STOPPED;
744        }
745      }
746      else
747      {
748        if (configuredAsService)
749        {
750          if (windowsNetStopPresent)
751          {
752            // stop-ds.bat is being called through net stop, so return
753            // STOP_USING_SYSTEM_CALL or RESTART_USING_SYSTEM_CALL so that the
754            // batch file actually stops the server.
755            if (restartPresent)
756            {
757              if (quietMode)
758              {
759                returnValue = RESTART_USING_SYSTEM_CALL_QUIET;
760              }
761              else
762              {
763                returnValue = RESTART_USING_SYSTEM_CALL;
764              }
765            }
766            else
767            {
768              returnValue = STOP_USING_SYSTEM_CALL;
769            }
770          }
771          else
772          {
773            if (restartPresent)
774            {
775              returnValue = RESTART_AS_WINDOW_SERVICE;
776            }
777            else
778            {
779              returnValue = STOP_AS_WINDOW_SERVICE;
780            }
781            // Display a message informing that we are going to the server.
782
783            LocalizableMessage message = INFO_STOPDS_GOING_TO_STOP.get();
784            out.println(message);
785          }
786        }
787        else
788        {
789          // Display a message informing that we are going to the server.
790
791          LocalizableMessage message = INFO_STOPDS_GOING_TO_STOP.get();
792          out.println(message);
793
794          if (restartPresent)
795          {
796            if (quietMode)
797            {
798              returnValue = RESTART_USING_SYSTEM_CALL_QUIET;
799            }
800            else
801            {
802              returnValue = RESTART_USING_SYSTEM_CALL;
803            }
804          }
805          else
806          {
807            returnValue = STOP_USING_SYSTEM_CALL;
808          }
809        }
810      }
811    }
812    return returnValue;
813  }
814}
815