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 2008-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.tasks;
018
019import static org.opends.messages.TaskMessages.*;
020import static org.opends.server.util.ServerConstants.*;
021import static org.opends.server.util.StaticUtils.*;
022
023import org.forgerock.i18n.LocalizableMessage;
024import org.forgerock.i18n.slf4j.LocalizedLogger;
025import org.forgerock.opendj.ldap.ByteString;
026import org.forgerock.opendj.ldap.ResultCode;
027import org.opends.server.api.ClientConnection;
028import org.opends.server.api.ConnectionHandler;
029import org.opends.server.backends.task.Task;
030import org.opends.server.backends.task.TaskState;
031import org.opends.server.core.DirectoryServer;
032import org.opends.server.types.Attribute;
033import org.forgerock.opendj.ldap.schema.AttributeType;
034import org.opends.server.types.DirectoryException;
035import org.opends.server.types.DisconnectReason;
036import org.opends.server.types.Entry;
037import org.opends.server.types.Operation;
038import org.opends.server.types.Privilege;
039
040/**
041 * This class provides an implementation of a Directory Server task that can be
042 * used to terminate a client connection.
043 */
044public class DisconnectClientTask extends Task
045{
046  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
047
048  /** Indicates whether to send a notification message to the client. */
049  private boolean notifyClient;
050
051  /** The connection ID for the client connection to terminate. */
052  private long connectionID;
053
054  /** The disconnect message to send to the client. */
055  private LocalizableMessage disconnectMessage;
056
057  /** {@inheritDoc} */
058  @Override
059  public LocalizableMessage getDisplayName() {
060    return INFO_TASK_DISCONNECT_CLIENT_NAME.get();
061  }
062
063  /** {@inheritDoc} */
064  @Override
065  public void initializeTask() throws DirectoryException
066  {
067    // If the client connection is available, then make sure the client has the
068    // DISCONNECT_CLIENT privilege.
069    Operation operation = getOperation();
070    if (operation != null)
071    {
072      ClientConnection conn = operation.getClientConnection();
073      if (! conn.hasPrivilege(Privilege.DISCONNECT_CLIENT, operation))
074      {
075        LocalizableMessage message = ERR_TASK_DISCONNECT_NO_PRIVILEGE.get();
076        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
077                                     message);
078      }
079    }
080
081    final Entry taskEntry = getTaskEntry();
082    connectionID = getConnectionID(taskEntry);
083    if (connectionID < 0)
084    {
085      LocalizableMessage message =
086          ERR_TASK_DISCONNECT_NO_CONN_ID.get(ATTR_TASK_DISCONNECT_CONN_ID);
087      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
088    }
089
090    notifyClient = mustNotifyClient(taskEntry);
091    disconnectMessage = getDisconnectMessage(taskEntry);
092  }
093
094  private long getConnectionID(Entry taskEntry) throws DirectoryException
095  {
096    final AttributeType attrType = DirectoryServer.getSchema().getAttributeType(ATTR_TASK_DISCONNECT_CONN_ID);
097    for (Attribute a : taskEntry.getAttribute(attrType))
098    {
099      for (ByteString v : a)
100      {
101        try
102        {
103          return Long.parseLong(v.toString());
104        }
105        catch (Exception e)
106        {
107          LocalizableMessage message = ERR_TASK_DISCONNECT_INVALID_CONN_ID.get(v);
108          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, e);
109        }
110      }
111    }
112    return -1;
113  }
114
115  private boolean mustNotifyClient(Entry taskEntry) throws DirectoryException
116  {
117    final AttributeType attrType = DirectoryServer.getSchema().getAttributeType(ATTR_TASK_DISCONNECT_NOTIFY_CLIENT);
118    for (Attribute a : taskEntry.getAttribute(attrType))
119    {
120      for (ByteString v : a)
121      {
122        final String stringValue = toLowerCase(v.toString());
123        if ("true".equals(stringValue))
124        {
125          return true;
126        }
127        else if ("false".equals(stringValue))
128        {
129          return false;
130        }
131        else
132        {
133          LocalizableMessage message = ERR_TASK_DISCONNECT_INVALID_NOTIFY_CLIENT.get(stringValue);
134          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
135        }
136      }
137    }
138    return false;
139  }
140
141  private LocalizableMessage getDisconnectMessage(Entry taskEntry)
142  {
143    AttributeType attrType = DirectoryServer.getSchema().getAttributeType(ATTR_TASK_DISCONNECT_MESSAGE);
144    for (Attribute a : taskEntry.getAttribute(attrType))
145    {
146      for (ByteString v : a)
147      {
148        return LocalizableMessage.raw(v.toString());
149      }
150    }
151    return INFO_TASK_DISCONNECT_GENERIC_MESSAGE.get();
152  }
153
154  @Override
155  protected TaskState runTask()
156  {
157    final ClientConnection clientConnection = getClientConnection();
158    if (clientConnection == null)
159    {
160      logger.error(ERR_TASK_DISCONNECT_NO_SUCH_CONNECTION, connectionID);
161      return TaskState.COMPLETED_WITH_ERRORS;
162    }
163
164    clientConnection.disconnect(DisconnectReason.ADMIN_DISCONNECT, notifyClient, disconnectMessage);
165    return TaskState.COMPLETED_SUCCESSFULLY;
166  }
167
168  private ClientConnection getClientConnection()
169  {
170    for (ConnectionHandler<?> handler : DirectoryServer.getConnectionHandlers())
171    {
172      for (ClientConnection c : handler.getClientConnections())
173      {
174        if (c.getConnectionID() == connectionID)
175        {
176          return c;
177        }
178      }
179    }
180    return null;
181  }
182}