pssht  latest
SSH server library written in PHP
CLI.php
1 <?php
2 /*
3 * This file is part of pssht.
4 *
5 * (c) François Poirotte <clicky@erebot.net>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10 declare(ticks=1);
11 
16 
17 function escape($data)
18 {
19  return addcslashes($data, "\x00..\x1F\x7F..\xFF");
20 }
21 
22 function signal_handler($signo)
23 {
24  $logging = \Plop\Plop::getInstance();
25  $logging->info(
26  "Received signal #%d. Shutting down...",
27  array($signo)
28  );
29  exit(0);
30 }
31 
32 function main($confFile = 'pssht.xml')
33 {
34  $hasSignalDispatch = function_exists('pcntl_signal_dispatch');
35  if (extension_loaded('pcntl')) {
36  pcntl_signal(SIGTERM, 'signal_handler');
37  pcntl_signal(SIGINT, 'signal_handler');
38  }
39 
40  $home = getenv('HOME');
41  $user = getenv('USER');
42  if (extension_loaded('posix')) {
43  $entry = posix_getpwuid(posix_geteuid());
44  $home = $entry['dir'];
45  $user = $entry['name'];
46  }
47 
48  // DIC
49  $container = new ContainerBuilder();
50  $container->setParameter('CWD', getcwd());
51  $container->setParameter('HOME', $home);
52  $container->setParameter('USER', $user);
53  $container->setParameter('pssht.base_dir', dirname(__DIR__));
54 
55  $loader = new XmlFileLoader($container, new FileLocator(getcwd()));
56  try {
57  $loader->load($confFile);
58  } catch (\InvalidArgumentException $e) {
59  $logging = \Plop\Plop::getInstance();
60  $logging->error($e->getMessage());
61  exit(1);
62  }
63  $container->get('logging', ContainerInterface::NULL_ON_INVALID_REFERENCE);
64  $logging = \Plop\Plop::getInstance();
65  $logging->info(
66  "pssht %s is starting (PID %d)",
67  array(PSSHT_VERSION, getmypid())
68  );
69 
70  // Elliptic curves
71  \fpoirotte\Pssht\ECC\Curve::initialize();
72 
73  // Pre-load algorithms
75 
76  // Sockets
77  $sockets = array('servers' => array(), 'clients' => array());
78  $clients = array();
79 
80  $listen = (array) $container->getParameter('listen');
81  foreach ($listen as $spec) {
82  $socket = stream_socket_server("tcp://$spec");
83  $sockets['servers'][] = $socket;
84  $address = stream_socket_get_name($socket, false);
85  $logging->info("Listening for new connections on %s", array($address));
86  }
87 
88  while (true) {
89  $read = array_merge($sockets['servers'], $sockets['clients']);
90  $except = $read;
91  $write = array();
92 
93  foreach ($clients as $id => $client) {
94  if (count($client->getEncoder()->getBuffer())) {
95  $write[] = $sockets['clients'][$id];
96  }
97  }
98 
99  if (@stream_select($read, $write, $except, 2) === false) {
100  $logging->error(
101  'Error while waiting for activity on sockets: %s',
102  array(socket_strerror(socket_last_error()))
103  );
104  continue;
105  }
106 
107  if ($hasSignalDispatch) {
108  function_exists('pcntl_signal_dispatch');
109  }
110 
111  foreach ($read as $socket) {
112  if (in_array($socket, $sockets['servers'], true)) {
113  $new = stream_socket_accept($socket);
114  if ($new === false) {
115  $logging->error(
116  'Could not accept new client: %s',
117  array(socket_strerror(socket_last_error()))
118  );
119  continue;
120  }
121 
122  for ($id = 0; isset($sockets['clients'][$id]); $id++) {
123  // Nothing to do.
124  }
125  $sockets['clients'][$id] = $new;
126  $peer = stream_socket_get_name($new, true);
127  $logging->info(
128  '#%(id)d New client connected from %(peer)s',
129  array('id' => $id, 'peer' => $peer)
130  );
131  $client = $container->get('client');
132  $clients[$id] = $client;
133  $client->setAddress(substr($peer, 0, strrpos($peer, ':')));
134  continue;
135  }
136 
137  $data = fread($socket, 8192);
138  $peer = stream_socket_get_name($socket, true);
139  $close = false;
140  if ($data === '') {
141  $id = array_search($socket, $sockets['clients'], true);
142  $logging->info(
143  '#%(id)d Client disconnected from %(peer)s (socket closed)',
144  array('id' => $id, 'peer' => $peer)
145  );
146  $close = true;
147  } elseif ($data !== false) {
148  $length = strlen($data);
149  $id = array_search($socket, $sockets['clients'], true);
150  $clients[$id]->getDecoder()->getBuffer()->push($data);
151 
152  $logging->log(
153  5,
154  '#%(id)d Received %(length)d bytes from %(peer)s',
155  array('id' => $id, 'peer' => $peer, 'length' => $length)
156  );
157  $logging->log(5, '%s', array(escape($data)));
158 
159  // Process messages in the buffer.
160  try {
161  $close = true;
162  while ($clients[$id]->readMessage()) {
163  // Each message gets processed by readMessage().
164  }
165  // Never reached when an exception
166  // is raised by readMessage().
167  $close = false;
168  } catch (\fpoirotte\Pssht\Messages\DISCONNECT $e) {
169  $logging->info(
170  '#%(id)d Client disconnected from %(peer)s ' .
171  '(DISCONNECT message received)',
172  array('id' => $id, 'peer' => $peer)
173  );
174  } catch (\Exception $e) {
175  $logging->exception(
176  '#%(id)d Client disconnected from %(peer)s ' .
177  'due to exception',
178  $e,
179  array('id' => $id, 'peer' => $peer)
180  );
181  }
182  }
183 
184  if ($close) {
185  fclose($socket);
186  unset($sockets['clients'][$id]);
187  unset($clients[$id]);
188  }
189  }
190 
191  foreach ($write as $socket) {
192  $id = array_search($socket, $sockets['clients'], true);
193  if ($id === false) {
194  continue;
195  }
196 
197  $peer = stream_socket_get_name($socket, true);
198  $buffer = $clients[$id]->getEncoder()->getBuffer();
199  $size = count($buffer);
200  $data = $buffer->get($size);
201  while ($size > 0) {
202  $written = fwrite($socket, $data);
203  if ($written === false) {
204  break;
205  }
206 
207  $logging->log(
208  5,
209  "#%(id)d Sent %(written)d bytes to %(peer)s",
210  array('id' => $id, 'peer' => $peer, 'written' => $written)
211  );
212  $logging->log(5, '%s', array(escape(substr($data, 0, $written))));
213  $data = substr($data, $written);
214  $clients[$id]->updateWriteStats($written);
215  $size -= $written;
216  }
217 
218  if (!$clients[$id]->isConnected()) {
219  fclose($socket);
220  unset($sockets['clients'][$id]);
221  unset($clients[$id]);
222  }
223  }
224  }
225 }