blob: a67968f58160c452356d0a2ade3b62e0fda763aa [file] [log] [blame]
Felix Meschbergerefb2d082008-08-19 13:18:47 +00001/*
Richard S. Hall59aef192009-04-25 14:50:37 +00002 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
Felix Meschbergerefb2d082008-08-19 13:18:47 +000017package org.apache.felix.shell.remote;
18
Felix Meschbergerefb2d082008-08-19 13:18:47 +000019import java.io.IOException;
20import java.io.PrintStream;
Felix Meschberger35901032009-01-30 22:43:37 +000021import java.net.InetAddress;
Felix Meschbergerefb2d082008-08-19 13:18:47 +000022import java.net.ServerSocket;
23import java.net.Socket;
24import java.net.SocketException;
Felix Meschberger51825702009-02-02 07:07:38 +000025import java.net.SocketTimeoutException;
Felix Meschberger73bf1232009-02-02 08:28:42 +000026import java.util.HashSet;
27import java.util.Set;
Felix Meschbergerefb2d082008-08-19 13:18:47 +000028
Felix Meschbergerd7c324d2008-08-19 14:10:48 +000029import org.osgi.framework.BundleContext;
30
Felix Meschbergerefb2d082008-08-19 13:18:47 +000031/**
32 * Implements a simple listener that will accept a single connection.
Felix Meschbergerefb2d082008-08-19 13:18:47 +000033 */
34class Listener
35{
Richard S. Hall55900ec2009-04-25 16:57:58 +000036 private final int m_port;
37 private final String m_ip;
38 private final Thread m_listenerThread;
39 private final Acceptor m_acceptor;
40 private final AtomicInteger m_useCounter;
41 private final int m_maxConnections;
42 private final int m_soTimeout;
43 private final Set m_connections;
44 private final ServiceMediator m_services;
Felix Meschbergerefb2d082008-08-19 13:18:47 +000045
Felix Meschbergerefb2d082008-08-19 13:18:47 +000046 /**
47 * Activates this listener on a listener thread (telnetconsole.Listener).
48 */
Richard S. Hall55900ec2009-04-25 16:57:58 +000049 public Listener(BundleContext context, ServiceMediator services) throws IOException
Felix Meschbergerefb2d082008-08-19 13:18:47 +000050 {
Richard S. Hall55900ec2009-04-25 16:57:58 +000051 m_services = services;
Felix Meschbergerd7c324d2008-08-19 14:10:48 +000052 //configure from framework property
Richard S. Hall55900ec2009-04-25 16:57:58 +000053 m_ip = getProperty(context, "osgi.shell.telnet.ip", "127.0.0.1");
54 m_port = getProperty(context, "osgi.shell.telnet.port", 6666);
55 m_soTimeout = getProperty(context, "osgi.shell.telnet.socketTimeout", 0);
56 m_maxConnections = getProperty(context, "osgi.shell.telnet.maxconn", 2);
Richard S. Hall59aef192009-04-25 14:50:37 +000057 m_useCounter = new AtomicInteger(0);
Felix Meschberger73bf1232009-02-02 08:28:42 +000058 m_connections = new HashSet();
Richard S. Hall55900ec2009-04-25 16:57:58 +000059 m_acceptor = new Acceptor();
60 m_listenerThread = new Thread(m_acceptor, "telnetconsole.Listener");
Richard S. Hall59aef192009-04-25 14:50:37 +000061 m_listenerThread.start();
Felix Meschbergerefb2d082008-08-19 13:18:47 +000062 }//activate
Richard S. Hall8a277a72010-08-24 14:11:47 +000063
Richard S. Hall55900ec2009-04-25 16:57:58 +000064 public ServiceMediator getServices()
65 {
66 return m_services;
67 }
Felix Meschbergerefb2d082008-08-19 13:18:47 +000068
Felix Meschbergerefb2d082008-08-19 13:18:47 +000069 /**
70 * Deactivates this listener.
71 * <p/>
72 * The listener's socket will be closed, which should cause an interrupt in the
73 * listener thread and allow for it to return. The calling thread joins the listener
74 * thread until it returns (to ensure a clean stop).
75 */
76 public void deactivate()
77 {
78 try
79 {
Felix Meschbergerefb2d082008-08-19 13:18:47 +000080 //wait for the listener thread
Richard S. Hall55900ec2009-04-25 16:57:58 +000081 m_acceptor.close();
Richard S. Hall59aef192009-04-25 14:50:37 +000082 m_listenerThread.join();
Felix Meschbergerefb2d082008-08-19 13:18:47 +000083 }
Richard S. Hall59aef192009-04-25 14:50:37 +000084 catch (Exception ex)
Felix Meschbergerefb2d082008-08-19 13:18:47 +000085 {
Richard S. Hall55900ec2009-04-25 16:57:58 +000086 m_services.error("Listener::deactivate()", ex);
Felix Meschbergerefb2d082008-08-19 13:18:47 +000087 }
Richard S. Hall59aef192009-04-25 14:50:37 +000088
Felix Meschberger73bf1232009-02-02 08:28:42 +000089 // get the active connections (and clear the list)
90 // we have to work on a copy, since stopping any active connection
91 // will try to remove itself from the set, which might cause a
92 // ConcurrentModificationException if we would iterate over the list
93 Shell[] connections;
Richard S. Hall59aef192009-04-25 14:50:37 +000094 synchronized (m_connections)
Felix Meschberger73bf1232009-02-02 08:28:42 +000095 {
Richard S. Hall59aef192009-04-25 14:50:37 +000096 connections = (Shell[]) m_connections.toArray(new Shell[m_connections.size()]);
Felix Meschberger73bf1232009-02-02 08:28:42 +000097 m_connections.clear();
98 }
99
100 // now terminate all active connections
Richard S. Hall59aef192009-04-25 14:50:37 +0000101 for (int i = 0; i < connections.length; i++)
Felix Meschberger73bf1232009-02-02 08:28:42 +0000102 {
103 connections[i].terminate();
104 }
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000105 }//deactivate
106
107 /**
108 * Class that implements the listener's accept logic as a <tt>Runnable</tt>.
109 */
110 private class Acceptor implements Runnable
111 {
Richard S. Hall55900ec2009-04-25 16:57:58 +0000112 private volatile boolean m_stop = false;
113 private final ServerSocket m_serverSocket;
114
115 Acceptor() throws IOException
116 {
117 m_serverSocket = new ServerSocket(m_port, 1, InetAddress.getByName(m_ip));
118 m_serverSocket.setSoTimeout(m_soTimeout);
119 }
120
121 public void close() throws IOException
122 {
123 m_stop = true;
124 m_serverSocket.close();
125 }
126
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000127 /**
128 * Listens constantly to a server socket and handles incoming connections.
129 * One connection will be accepted and routed into the shell, all others will
130 * be notified and closed.
131 * <p/>
132 * The mechanism that should allow the thread to unblock from the ServerSocket.accept() call
133 * is currently closing the ServerSocket from another thread. When the stop flag is set,
134 * this should cause the thread to return and stop.
135 */
136 public void run()
137 {
138 try
139 {
140 /*
Richard S. Hall59aef192009-04-25 14:50:37 +0000141 A server socket is opened with a connectivity queue of a size specified
142 in int floodProtection. Concurrent login handling under normal circumstances
143 should be handled properly, but denial of service attacks via massive parallel
144 program logins should be prevented with this.
145 */
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000146 do
147 {
148 try
149 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000150 Socket s = m_serverSocket.accept();
151 if (m_useCounter.get() >= m_maxConnections)
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000152 {
153 //reject with message
Richard S. Hall59aef192009-04-25 14:50:37 +0000154 PrintStream out = new PrintStream(s.getOutputStream());
155 out.print(INUSE_MESSAGE);
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000156 out.flush();
157 //close
158 out.close();
159 s.close();
160 }
161 else
162 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000163 m_useCounter.increment();
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000164 //run on the connection thread
Richard S. Hall59aef192009-04-25 14:50:37 +0000165 Thread connectionThread = new Thread(new Shell(Listener.this, s, m_useCounter));
166 connectionThread.setName("telnetconsole.shell remote=" + s.getRemoteSocketAddress());
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000167 connectionThread.start();
168 }
169 }
Richard S. Hall59aef192009-04-25 14:50:37 +0000170 catch (SocketException ex)
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000171 {
172 }
Richard S. Hall59aef192009-04-25 14:50:37 +0000173 catch (SocketTimeoutException ste)
174 {
Felix Meschberger51825702009-02-02 07:07:38 +0000175 // Caught socket timeout exception. continue
176 }
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000177 }
Richard S. Hall59aef192009-04-25 14:50:37 +0000178 while (!m_stop);
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000179
180 }
Richard S. Hall59aef192009-04-25 14:50:37 +0000181 catch (IOException e)
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000182 {
Richard S. Hall55900ec2009-04-25 16:57:58 +0000183 m_services.error("Listener.Acceptor::run()", e);
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000184 }
185 }//run
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000186 }//inner class Acceptor
187
Richard S. Hall59aef192009-04-25 14:50:37 +0000188 private static final String INUSE_MESSAGE = "Connection refused.\r\n" + "All possible connections are currently being used.\r\n";
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000189
Richard S. Hall59aef192009-04-25 14:50:37 +0000190 private int getProperty(BundleContext bundleContext, String propName, int defaultValue)
Felix Meschbergerd7c324d2008-08-19 14:10:48 +0000191 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000192 String propValue = bundleContext.getProperty(propName);
193 if (propValue != null)
Felix Meschbergerd7c324d2008-08-19 14:10:48 +0000194 {
195 try
196 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000197 return Integer.parseInt(propValue);
Felix Meschbergerd7c324d2008-08-19 14:10:48 +0000198 }
Richard S. Hall59aef192009-04-25 14:50:37 +0000199 catch (NumberFormatException ex)
Felix Meschbergerd7c324d2008-08-19 14:10:48 +0000200 {
Richard S. Hall55900ec2009-04-25 16:57:58 +0000201 m_services.error("Listener::activate()", ex);
Felix Meschbergerd7c324d2008-08-19 14:10:48 +0000202 }
203 }
204
205 return defaultValue;
206 }
207
Richard S. Hall59aef192009-04-25 14:50:37 +0000208 private String getProperty(BundleContext bundleContext, String propName, String defaultValue)
Felix Meschberger35901032009-01-30 22:43:37 +0000209 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000210 String propValue = bundleContext.getProperty(propName);
211 if (propValue != null)
Felix Meschberger35901032009-01-30 22:43:37 +0000212 {
Felix Meschberger73bf1232009-02-02 08:28:42 +0000213 return propValue;
Felix Meschberger35901032009-01-30 22:43:37 +0000214 }
215
216 return defaultValue;
217 }
218
Felix Meschberger73bf1232009-02-02 08:28:42 +0000219 /**
220 * Registers the given {@link Shell} instance handling a remote connection
221 * to this listener.
222 * <p>
223 * This method is called by the {@link Shell#run()} method to register the
224 * remote connection for it to be terminated in case this listener is
225 * {@link #deactivate() deactivated} before the remote connection is
226 * terminated.
227 *
228 * @param connection The {@link Shell} connection to register
229 */
Richard S. Hall59aef192009-04-25 14:50:37 +0000230 void registerConnection(Shell connection)
Felix Meschberger73bf1232009-02-02 08:28:42 +0000231 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000232 synchronized (m_connections)
Felix Meschberger73bf1232009-02-02 08:28:42 +0000233 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000234 m_connections.add(connection);
Felix Meschberger73bf1232009-02-02 08:28:42 +0000235 }
236 }
237
238 /**
239 * Unregisters the given {@link Shell} instance handling a remote connection
240 * from this listener.
241 * <p>
242 * This method is called when the {@link Shell#run()} method terminates to
243 * inform this listener instance that the remote connection has ended and
244 * thus does not need to be cleaned up when this listener terminates.
245 *
246 * @param connection The {@link Shell} connection to unregister
247 */
Richard S. Hall59aef192009-04-25 14:50:37 +0000248 void unregisterConnection(Shell connection)
Felix Meschberger73bf1232009-02-02 08:28:42 +0000249 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000250 synchronized (m_connections)
Felix Meschberger73bf1232009-02-02 08:28:42 +0000251 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000252 m_connections.remove(connection);
Felix Meschberger73bf1232009-02-02 08:28:42 +0000253 }
254 }
Richard S. Hall55900ec2009-04-25 16:57:58 +0000255}//class Listener