shell_server.c
Go to the documentation of this file.
1 /**
2  * @file shell_server.c
3  * @brief SSH secure shell server
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2019-2024 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneSSH Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @author Oryx Embedded SARL (www.oryx-embedded.com)
28  * @version 2.4.0
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL SHELL_TRACE_LEVEL
33 
34 //Dependencies
35 #include "ssh/ssh.h"
36 #include "shell/shell_server.h"
37 #include "shell/shell_server_pty.h"
39 #include "debug.h"
40 
41 //Check SSH stack configuration
42 #if (SHELL_SERVER_SUPPORT == ENABLED)
43 
44 
45 /**
46  * @brief Initialize settings with default values
47  * @param[out] settings Structure that contains shell server settings
48  **/
49 
51 {
52  uint_t i;
53 
54  //Initialize task parameters
55  for(i = 0; i < SHELL_SERVER_MAX_SESSIONS; i++)
56  {
57  //Default task parameters
58  settings->task[i] = OS_TASK_DEFAULT_PARAMS;
59  settings->task[i].stackSize = SSH_SERVER_STACK_SIZE;
60  settings->task[i].priority = SSH_SERVER_PRIORITY;
61  }
62 
63  //SSH server context
64  settings->sshServerContext = NULL;
65 
66  //Shell sessions
67  settings->numSessions = 0;
68  settings->sessions = NULL;
69 
70  //User verification callback function
71  settings->checkUserCallback = NULL;
72  //Command line processing callback function
73  settings->commandLineCallback = NULL;
74  //Session closing callback function
75  settings->closeCallback = NULL;
76 }
77 
78 
79 /**
80  * @brief Initialize shell server context
81  * @param[in] context Pointer to the shell server context
82  * @param[in] settings Shell server specific settings
83  * @return Error code
84  **/
85 
87  const ShellServerSettings *settings)
88 {
89  uint_t i;
90  ShellServerSession *session;
91 
92  //Debug message
93  TRACE_INFO("Initializing shell server...\r\n");
94 
95  //Ensure the parameters are valid
96  if(context == NULL || settings == NULL)
98 
99  //Invalid shell sessions?
100  if(settings->sessions == NULL || settings->numSessions < 1 ||
102  {
104  }
105 
106  //Clear shell server context
107  osMemset(context, 0, sizeof(ShellServerContext));
108 
109  //Save user settings
110  context->sshServerContext = settings->sshServerContext;
111  context->numSessions = settings->numSessions;
112  context->sessions = settings->sessions;
113  context->checkUserCallback = settings->checkUserCallback;
114  context->commandLineCallback = settings->commandLineCallback;
115  context->closeCallback = settings->closeCallback;
116 
117  //Loop through shell sessions
118  for(i = 0; i < context->numSessions; i++)
119  {
120  //Point to the structure describing the current session
121  session = &context->sessions[i];
122 
123  //Initialize the structure representing the shell session
124  osMemset(session, 0, sizeof(ShellServerSession));
125 
126  //Initialize task parameters
127  session->taskParams = settings->task[i];
128  session->taskId = OS_INVALID_TASK_ID;
129 
130  //Attach shell server context
131  session->context = context;
132 
133  //Create an event object to manage session lifetime
134  if(!osCreateEvent(&session->startEvent))
135  return ERROR_OUT_OF_RESOURCES;
136 
137  //Create an event object to manage session events
138  if(!osCreateEvent(&session->event))
139  return ERROR_OUT_OF_RESOURCES;
140  }
141 
142  //Create an event object to poll the state of channels
143  if(!osCreateEvent(&context->event))
144  return ERROR_OUT_OF_RESOURCES;
145 
146  //Successful initialization
147  return NO_ERROR;
148 }
149 
150 
151 /**
152  * @brief Start shell server
153  * @param[in] context Pointer to the shell server context
154  * @return Error code
155  **/
156 
158 {
159  error_t error;
160  uint_t i;
161  ShellServerSession *session;
162 
163  //Make sure the shell server context is valid
164  if(context == NULL)
166 
167  //Debug message
168  TRACE_INFO("Starting shell server...\r\n");
169 
170  //Make sure the shell server is not already running
171  if(context->running)
172  return ERROR_ALREADY_RUNNING;
173 
174  //Register channel request processing callback
175  error = sshServerRegisterChannelRequestCallback(context->sshServerContext,
177  //Any error to report?
178  if(error)
179  return error;
180 
181  //Loop through the shell sessions
182  for(i = 0; i < context->numSessions; i++)
183  {
184  //Point to the current session
185  session = &context->sessions[i];
186 
187  //Create a task
188  session->taskId = osCreateTask("Shell Server",
189  (OsTaskCode) shellServerTask, session, &session->taskParams);
190 
191  //Failed to create task?
192  if(session->taskId == OS_INVALID_TASK_ID)
193  return ERROR_OUT_OF_RESOURCES;
194  }
195 
196  //Successful processing
197  return NO_ERROR;
198 }
199 
200 
201 /**
202  * @brief Set welcome banner
203  * @param[in] session Handle referencing a shell session
204  * @param[in] banner NULL-terminated string containing the banner message
205  * @return Error code
206  **/
207 
209  const char_t *banner)
210 {
211  size_t n;
212 
213  //Check parameters
214  if(session == NULL || banner == NULL)
216 
217  //Retrieve the length of the banner message
218  n = osStrlen(banner);
219 
220  //Check the length of the string
222  return ERROR_INVALID_LENGTH;
223 
224  //Copy the banner message
225  osMemcpy(session->buffer, banner, n);
226 
227  //Save the length of the banner message
228  session->bufferLen = n;
229  session->bufferPos = 0;
230 
231  //Successful processing
232  return NO_ERROR;
233 }
234 
235 
236 /**
237  * @brief Set shell prompt
238  * @param[in] session Handle referencing a shell session
239  * @param[in] prompt NULL-terminated string containing the prompt to be used
240  * @return Error code
241  **/
242 
244  const char_t *prompt)
245 {
246  //Check parameters
247  if(session == NULL || prompt == NULL)
249 
250  //Check the length of the prompt string
252  return ERROR_INVALID_LENGTH;
253 
254  //Set the shell prompt to be used
255  osStrcpy(session->prompt, prompt);
256  //Save the length of the prompt string
257  session->promptLen = osStrlen(prompt);
258 
259  //Successful processing
260  return NO_ERROR;
261 }
262 
263 
264 /**
265  * @brief Set timeout for read/write operations
266  * @param[in] session Handle referencing a shell session
267  * @param[in] timeout Maximum time to wait
268  * @return Error code
269  **/
270 
272 {
273  error_t error;
274 
275  //Valid shell session?
276  if(session != NULL)
277  {
278  //Set timeout for read/write operations
279  error = sshSetChannelTimeout(session->channel, timeout);
280  }
281  else
282  {
283  //Report an error
284  error = ERROR_INVALID_PARAMETER;
285  }
286 
287  //Return status code
288  return error;
289 }
290 
291 
292 /**
293  * @brief Write to stdout stream
294  * @param[in] session Handle referencing a shell session
295  * @param[in] data Pointer to a buffer containing the data to be written
296  * @param[in] length Number of data bytes to write
297  * @param[in] written Number of bytes that have been written (optional parameter)
298  * @param[in] flags Set of flags that influences the behavior of this function
299  * @return Error code
300  **/
301 
303  size_t length, size_t *written, uint_t flags)
304 {
305  error_t error;
306 
307  //Valid shell session?
308  if(session != NULL)
309  {
310  //Write data to the specified channel
311  error = sshWriteChannel(session->channel, data, length, written, flags);
312  }
313  else
314  {
315  //Report an error
316  error = ERROR_INVALID_PARAMETER;
317  }
318 
319  //Return status code
320  return error;
321 }
322 
323 
324 /**
325  * @brief Read from stdin stream
326  * @param[in] session Handle referencing a shell session
327  * @param[out] data Buffer where to store the incoming data
328  * @param[in] size Maximum number of bytes that can be read
329  * @param[out] received Actual number of bytes that have been read
330  * @param[in] flags Set of flags that influences the behavior of this function
331  * @return Error code
332  **/
333 
335  size_t size, size_t *received, uint_t flags)
336 {
337  error_t error;
338 
339  //Valid shell session?
340  if(session != NULL)
341  {
342  //Receive data from the specified channel
343  error = sshReadChannel(session->channel, data, size, received, flags);
344  }
345  else
346  {
347  //Report an error
348  error = ERROR_INVALID_PARAMETER;
349  }
350 
351  //Return status code
352  return error;
353 }
354 
355 
356 /**
357  * @brief Save command history
358  * @param[in] session Handle referencing a shell session
359  * @param[out] history Output buffer where to store the command history
360  * @param[in] size Size of the buffer, in bytes
361  * @param[out] length Actual length of the command history, in bytes
362  * @return Error code
363  **/
364 
366  size_t size, size_t *length)
367 {
368 #if (SHELL_SERVER_HISTORY_SUPPORT == ENABLED)
369  size_t i;
370 
371  //Check parameters
372  if(session == NULL || history == NULL || length == NULL)
374 
375  //If the output buffer is not large enough, then the oldest commands are
376  //discarded
377  for(i = 0; (session->historyLen - i) > size; )
378  {
379  //Each entry is terminated by a NULL character
380  while(i < session->historyLen && session->history[i] != '\0')
381  {
382  i++;
383  }
384 
385  //Skip the NULL terminator
386  if(i < session->historyLen)
387  {
388  i++;
389  }
390  }
391 
392  //Save the most recent commands
393  osMemcpy(history, session->history + i, session->historyLen - i);
394  //Return the length of the command history
395  *length = session->historyLen - i;
396 
397  //Successful processing
398  return NO_ERROR;
399 #else
400  //Not implemented
401  return ERROR_NOT_IMPLEMENTED;
402 #endif
403 }
404 
405 
406 /**
407  * @brief Restore command history
408  * @param[in] session Handle referencing a shell session
409  * @param[in] history Pointer to the buffer that contains the command history
410  * @param[in] length Length of the command history, in bytes
411  * @return Error code
412  **/
413 
415  const char_t *history, size_t length)
416 {
417 #if (SHELL_SERVER_HISTORY_SUPPORT == ENABLED)
418  size_t i;
419 
420  //Check parameters
421  if(session == NULL || history == NULL)
423 
424  //If the command history buffer is not large enough, then the oldest commands
425  //are discarded
426  for(i = 0; (length - i) > SHELL_SERVER_HISTORY_SIZE; )
427  {
428  //Each entry is terminated by a NULL character
429  while(i < length && history[i] != '\0')
430  {
431  i++;
432  }
433 
434  //Skip the NULL terminator
435  if(i < length)
436  {
437  i++;
438  }
439  }
440 
441  //Restore the most recent commands
442  osMemcpy(session->history, history, length - i);
443 
444  //Save the length of the command history
445  session->historyLen = length - i;
446  session->historyPos = length - i;
447 
448  //Properly terminate the last entry with a NULL character
449  if(session->historyLen > 0)
450  {
451  session->history[session->historyLen - 1] = '\0';
452  }
453 
454  //Successful processing
455  return NO_ERROR;
456 #else
457  //Not implemented
458  return ERROR_NOT_IMPLEMENTED;
459 #endif
460 }
461 
462 
463 /**
464  * @brief Clear command history
465  * @param[in] session Handle referencing a shell session
466  * @return Error code
467  **/
468 
470 {
471 #if (SHELL_SERVER_HISTORY_SUPPORT == ENABLED)
472  //Make sure the shell session is valid
473  if(session == NULL)
475 
476  //Clear all entries from command history
477  session->historyLen = 0;
478  session->historyPos = 0;
479 
480  //Successful processing
481  return NO_ERROR;
482 #else
483  //Not implemented
484  return ERROR_NOT_IMPLEMENTED;
485 #endif
486 }
487 
488 
489 /**
490  * @brief Shell server task
491  * @param[in] param Pointer to the shell session
492  **/
493 
494 void shellServerTask(void *param)
495 {
496  error_t error;
497  SshChannel *channel;
498  ShellServerContext *context;
499  ShellServerSession *session;
500 
501  //Task prologue
502  osEnterTask();
503 
504  //Point to the shell session
505  session = (ShellServerSession *) param;
506  //Point to the shell server context
507  context = session->context;
508 
509  //Debug message
510  TRACE_INFO("Starting shell task...\r\n");
511 
512  //Initialize status code
513  error = NO_ERROR;
514 
515  //Process connection requests
516  while(1)
517  {
518  //Wait for an connection request
519  osWaitForEvent(&session->startEvent, INFINITE_DELAY);
520 
521  //Debug message
522  TRACE_INFO("Starting shell session...\r\n");
523 
524  //Retrieve SSH channel handle
525  channel = session->channel;
526 
527  //Check session state
528  if(session->state == SHELL_SERVER_SESSION_STATE_OPEN)
529  {
530  //Set timeout for read/write operations
532 
533  //Any banner message?
534  if(session->bufferLen > 0)
535  {
536  //Display welcome banner
537  error = sshWriteChannel(channel, session->buffer,
538  session->bufferLen, NULL, 0);
539  }
540 
541  //Check status code
542  if(!error)
543  {
544  //Display shell prompt
545  error = sshWriteChannel(channel, session->prompt,
546  osStrlen(session->prompt), NULL, 0);
547  }
548 
549  //Initialize variables
550  session->bufferLen = 0;
551  session->bufferPos = 0;
552  session->escSeqLen = 0;
553 
554  //Process user commands
555  while(!error)
556  {
557  SshChannelEventDesc eventDesc[1];
558 
559  //Specifying the events the application is interested in
560  eventDesc[0].channel = channel;
561  eventDesc[0].eventMask = SSH_CHANNEL_EVENT_RX_READY;
562  eventDesc[0].eventFlags = 0;
563 
564  //Wait for the channel to become ready to perform I/O
565  error = sshPollChannels(eventDesc, 1, &session->event,
567 
568  //Check status code
569  if(error == NO_ERROR || error == ERROR_TIMEOUT)
570  {
571  //Window resized?
572  if(session->windowResize)
573  {
574  //Process window resize event
575  error = shellServerProcessWindowResize(session);
576  }
577 
578  //Character received?
579  if(eventDesc[0].eventFlags != 0)
580  {
581  //Process received character
582  error = shellServerProcessChar(session);
583  }
584  else
585  {
586  //Wait for the next character
587  error = NO_ERROR;
588  }
589  }
590  else
591  {
592  //A communication error has occurred
593  break;
594  }
595  }
596 
597  //Invoke user-defined callback, if any
598  if(context->closeCallback != NULL)
599  {
600  //The session is about to close
601  context->closeCallback(session, session->channel->connection->user);
602  }
603  }
604  else if(session->state == SHELL_SERVER_SESSION_STATE_EXEC)
605  {
606  //Properly terminate the command line with a NULL character
607  session->buffer[session->bufferLen] = '\0';
608  //Process command line
609  error = shellServerProcessCommandLine(session, session->buffer);
610  }
611  else
612  {
613  //Just for sanity
614  }
615 
616  //Close SSH channel
617  sshCloseChannel(channel);
618 
619  //Mark the current session as closed
620  session->state = SHELL_SERVER_SESSION_STATE_CLOSED;
621 
622  //Debug message
623  TRACE_INFO("Shell session terminated...\r\n");
624  }
625 }
626 
627 #endif
unsigned int uint_t
Definition: compiler_port.h:50
char char_t
Definition: compiler_port.h:48
Debugging facilities.
#define TRACE_INFO(...)
Definition: debug.h:95
uint8_t n
error_t
Error codes.
Definition: error.h:43
@ ERROR_ALREADY_RUNNING
Definition: error.h:292
@ ERROR_TIMEOUT
Definition: error.h:95
@ ERROR_OUT_OF_RESOURCES
Definition: error.h:64
@ ERROR_NOT_IMPLEMENTED
Definition: error.h:66
@ NO_ERROR
Success.
Definition: error.h:44
@ ERROR_INVALID_LENGTH
Definition: error.h:111
@ ERROR_INVALID_PARAMETER
Invalid parameter.
Definition: error.h:47
uint8_t data[]
Definition: ethernet.h:222
#define osMemset(p, value, length)
Definition: os_port.h:135
#define osMemcpy(dest, src, length)
Definition: os_port.h:141
#define osStrlen(s)
Definition: os_port.h:165
#define INFINITE_DELAY
Definition: os_port.h:75
#define osStrcpy(s1, s2)
Definition: os_port.h:207
bool_t osWaitForEvent(OsEvent *event, systime_t timeout)
Wait until the specified event is in the signaled state.
const OsTaskParameters OS_TASK_DEFAULT_PARAMS
OsTaskId osCreateTask(const char_t *name, OsTaskCode taskCode, void *arg, const OsTaskParameters *params)
Create a task.
bool_t osCreateEvent(OsEvent *event)
Create an event object.
void(* OsTaskCode)(void *arg)
Task routine.
#define osEnterTask()
#define OS_INVALID_TASK_ID
uint32_t systime_t
System time.
error_t shellServerReadStream(ShellServerSession *session, void *data, size_t size, size_t *received, uint_t flags)
Read from stdin stream.
Definition: shell_server.c:334
error_t shellServerInit(ShellServerContext *context, const ShellServerSettings *settings)
Initialize shell server context.
Definition: shell_server.c:86
void shellServerTask(void *param)
Shell server task.
Definition: shell_server.c:494
error_t shellServerStart(ShellServerContext *context)
Start shell server.
Definition: shell_server.c:157
error_t shellServerSetPrompt(ShellServerSession *session, const char_t *prompt)
Set shell prompt.
Definition: shell_server.c:243
error_t shellServerRestoreHistory(ShellServerSession *session, const char_t *history, size_t length)
Restore command history.
Definition: shell_server.c:414
error_t shellServerSaveHistory(ShellServerSession *session, char_t *history, size_t size, size_t *length)
Save command history.
Definition: shell_server.c:365
void shellServerGetDefaultSettings(ShellServerSettings *settings)
Initialize settings with default values.
Definition: shell_server.c:50
error_t shellServerSetBanner(ShellServerSession *session, const char_t *banner)
Set welcome banner.
Definition: shell_server.c:208
error_t shellServerSetTimeout(ShellServerSession *session, systime_t timeout)
Set timeout for read/write operations.
Definition: shell_server.c:271
error_t shellServerWriteStream(ShellServerSession *session, const void *data, size_t length, size_t *written, uint_t flags)
Write to stdout stream.
Definition: shell_server.c:302
error_t shellServerClearHistory(ShellServerSession *session)
Clear command history.
Definition: shell_server.c:469
SSH secure shell server.
@ SHELL_SERVER_SESSION_STATE_OPEN
Definition: shell_server.h:148
@ SHELL_SERVER_SESSION_STATE_EXEC
Definition: shell_server.h:149
@ SHELL_SERVER_SESSION_STATE_CLOSED
Definition: shell_server.h:146
#define SHELL_SERVER_MAX_SESSIONS
Definition: shell_server.h:58
#define SHELL_SERVER_BUFFER_SIZE
Definition: shell_server.h:72
#define ShellServerSession
Definition: shell_server.h:121
#define SHELL_SERVER_MAX_PROMPT_LEN
Definition: shell_server.h:93
#define ShellServerContext
Definition: shell_server.h:117
#define SHELL_SERVER_TICK_INTERVAL
Definition: shell_server.h:65
#define SHELL_SERVER_HISTORY_SIZE
Definition: shell_server.h:86
error_t shellServerChannelRequestCallback(SshChannel *channel, const SshString *type, const uint8_t *data, size_t length, void *param)
SSH channel request callback.
error_t shellServerProcessCommandLine(ShellServerSession *session, char_t *commandLine)
Command line processing.
Helper functions for SSH secure shell server.
error_t shellServerProcessChar(ShellServerSession *session)
Process received character.
error_t shellServerProcessWindowResize(ShellServerSession *session)
Process window resize event.
Pseudo-terminal emulation.
error_t sshSetChannelTimeout(SshChannel *channel, systime_t timeout)
Set timeout for read/write operations.
Definition: ssh.c:2027
error_t sshCloseChannel(SshChannel *channel)
Close channel.
Definition: ssh.c:2465
error_t sshPollChannels(SshChannelEventDesc *eventDesc, uint_t size, OsEvent *extEvent, systime_t timeout)
Wait for one of a set of channels to become ready to perform I/O.
Definition: ssh.c:2376
error_t sshWriteChannel(SshChannel *channel, const void *data, size_t length, size_t *written, uint_t flags)
Write data to the specified channel.
Definition: ssh.c:2051
error_t sshReadChannel(SshChannel *channel, void *data, size_t size, size_t *received, uint_t flags)
Receive data from the specified channel.
Definition: ssh.c:2180
Secure Shell (SSH)
#define SshChannel
Definition: ssh.h:887
@ SSH_CHANNEL_EVENT_RX_READY
Definition: ssh.h:1115
error_t sshServerRegisterChannelRequestCallback(SshServerContext *context, SshChannelReqCallback callback, void *param)
Register channel request callback function.
Definition: ssh_server.c:360
#define SSH_SERVER_PRIORITY
Definition: ssh_server.h:46
#define SSH_SERVER_STACK_SIZE
Definition: ssh_server.h:39
Shell server settings.
Definition: shell_server.h:182
ShellServerCloseCallback closeCallback
Session closing callback function.
Definition: shell_server.h:189
SshServerContext * sshServerContext
SSH server context.
Definition: shell_server.h:184
ShellServerCheckUserCallback checkUserCallback
User verification callback function.
Definition: shell_server.h:187
uint_t numSessions
Maximum number of shell sessions.
Definition: shell_server.h:185
OsTaskParameters task[SHELL_SERVER_MAX_SESSIONS]
Task parameters.
Definition: shell_server.h:183
ShellServerSession * sessions
Shell sessions.
Definition: shell_server.h:186
ShellServerCommandLineCallback commandLineCallback
Command line processing callback function.
Definition: shell_server.h:188
Structure describing channel events.
Definition: ssh.h:1560
uint_t eventMask
Requested events.
Definition: ssh.h:1562
SshChannel * channel
Handle to a channel to monitor.
Definition: ssh.h:1561
uint_t eventFlags
Returned events.
Definition: ssh.h:1563
uint8_t length
Definition: tcp.h:368
uint8_t flags
Definition: tcp.h:351