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-2026 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.6.2
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 Set exit status
358  * @param[in] session Handle referencing a shell session
359  * @param[in] exitStatus Exit status of the command
360  * @return Error code
361  **/
362 
364  int32_t exitStatus)
365 {
366  error_t error;
367 
368  //Valid shell session?
369  if(session != NULL)
370  {
371  //Set the exit status of the command
372  error = sshSetExitStatus(session->channel, exitStatus);
373  }
374  else
375  {
376  //Report an error
377  error = ERROR_INVALID_PARAMETER;
378  }
379 
380  //Return status code
381  return error;
382 }
383 
384 
385 /**
386  * @brief Save command history
387  * @param[in] session Handle referencing a shell session
388  * @param[out] history Output buffer where to store the command history
389  * @param[in] size Size of the buffer, in bytes
390  * @param[out] length Actual length of the command history, in bytes
391  * @return Error code
392  **/
393 
395  size_t size, size_t *length)
396 {
397 #if (SHELL_SERVER_HISTORY_SUPPORT == ENABLED)
398  size_t i;
399 
400  //Check parameters
401  if(session == NULL || history == NULL || length == NULL)
403 
404  //If the output buffer is not large enough, then the oldest commands are
405  //discarded
406  for(i = 0; (session->historyLen - i) > size; )
407  {
408  //Each entry is terminated by a NULL character
409  while(i < session->historyLen && session->history[i] != '\0')
410  {
411  i++;
412  }
413 
414  //Skip the NULL terminator
415  if(i < session->historyLen)
416  {
417  i++;
418  }
419  }
420 
421  //Save the most recent commands
422  osMemcpy(history, session->history + i, session->historyLen - i);
423  //Return the length of the command history
424  *length = session->historyLen - i;
425 
426  //Successful processing
427  return NO_ERROR;
428 #else
429  //Not implemented
430  return ERROR_NOT_IMPLEMENTED;
431 #endif
432 }
433 
434 
435 /**
436  * @brief Restore command history
437  * @param[in] session Handle referencing a shell session
438  * @param[in] history Pointer to the buffer that contains the command history
439  * @param[in] length Length of the command history, in bytes
440  * @return Error code
441  **/
442 
444  const char_t *history, size_t length)
445 {
446 #if (SHELL_SERVER_HISTORY_SUPPORT == ENABLED)
447  size_t i;
448 
449  //Check parameters
450  if(session == NULL || history == NULL)
452 
453  //If the command history buffer is not large enough, then the oldest commands
454  //are discarded
455  for(i = 0; (length - i) > SHELL_SERVER_HISTORY_SIZE; )
456  {
457  //Each entry is terminated by a NULL character
458  while(i < length && history[i] != '\0')
459  {
460  i++;
461  }
462 
463  //Skip the NULL terminator
464  if(i < length)
465  {
466  i++;
467  }
468  }
469 
470  //Restore the most recent commands
471  osMemcpy(session->history, history, length - i);
472 
473  //Save the length of the command history
474  session->historyLen = length - i;
475  session->historyPos = length - i;
476 
477  //Properly terminate the last entry with a NULL character
478  if(session->historyLen > 0)
479  {
480  session->history[session->historyLen - 1] = '\0';
481  }
482 
483  //Successful processing
484  return NO_ERROR;
485 #else
486  //Not implemented
487  return ERROR_NOT_IMPLEMENTED;
488 #endif
489 }
490 
491 
492 /**
493  * @brief Clear command history
494  * @param[in] session Handle referencing a shell session
495  * @return Error code
496  **/
497 
499 {
500 #if (SHELL_SERVER_HISTORY_SUPPORT == ENABLED)
501  //Make sure the shell session is valid
502  if(session == NULL)
504 
505  //Clear all entries from command history
506  session->historyLen = 0;
507  session->historyPos = 0;
508 
509  //Successful processing
510  return NO_ERROR;
511 #else
512  //Not implemented
513  return ERROR_NOT_IMPLEMENTED;
514 #endif
515 }
516 
517 
518 /**
519  * @brief Shell server task
520  * @param[in] param Pointer to the shell session
521  **/
522 
523 void shellServerTask(void *param)
524 {
525  error_t error;
526  SshChannel *channel;
527  ShellServerContext *context;
528  ShellServerSession *session;
529 
530  //Task prologue
531  osEnterTask();
532 
533  //Point to the shell session
534  session = (ShellServerSession *) param;
535  //Point to the shell server context
536  context = session->context;
537 
538  //Debug message
539  TRACE_INFO("Starting shell task...\r\n");
540 
541  //Initialize status code
542  error = NO_ERROR;
543 
544  //Process connection requests
545  while(1)
546  {
547  //Wait for an connection request
548  osWaitForEvent(&session->startEvent, INFINITE_DELAY);
549 
550  //Debug message
551  TRACE_INFO("Starting shell session...\r\n");
552 
553  //Retrieve SSH channel handle
554  channel = session->channel;
555 
556  //Check session state
557  if(session->state == SHELL_SERVER_SESSION_STATE_OPEN)
558  {
559  //Set timeout for read/write operations
561 
562  //Any banner message?
563  if(session->bufferLen > 0)
564  {
565  //Display welcome banner
566  error = sshWriteChannel(channel, session->buffer,
567  session->bufferLen, NULL, 0);
568  }
569 
570  //Check status code
571  if(!error)
572  {
573  //Display shell prompt
574  error = sshWriteChannel(channel, session->prompt,
575  osStrlen(session->prompt), NULL, 0);
576  }
577 
578  //Initialize variables
579  session->bufferLen = 0;
580  session->bufferPos = 0;
581  session->escSeqLen = 0;
582 
583  //Process user commands
584  while(!error)
585  {
586  SshChannelEventDesc eventDesc[1];
587 
588  //Specifying the events the application is interested in
589  eventDesc[0].channel = channel;
590  eventDesc[0].eventMask = SSH_CHANNEL_EVENT_RX_READY;
591  eventDesc[0].eventFlags = 0;
592 
593  //Wait for the channel to become ready to perform I/O
594  error = sshPollChannels(eventDesc, 1, &session->event,
596 
597  //Check status code
598  if(error == NO_ERROR || error == ERROR_TIMEOUT)
599  {
600  //Window resized?
601  if(session->windowResize)
602  {
603  //Process window resize event
604  error = shellServerProcessWindowResize(session);
605  }
606 
607  //Character received?
608  if(eventDesc[0].eventFlags != 0)
609  {
610  //Process received character
611  error = shellServerProcessChar(session);
612  }
613  else
614  {
615  //Wait for the next character
616  error = NO_ERROR;
617  }
618  }
619  else
620  {
621  //A communication error has occurred
622  break;
623  }
624  }
625 
626  //Invoke user-defined callback, if any
627  if(context->closeCallback != NULL)
628  {
629  //The session is about to close
630  context->closeCallback(session, session->channel->connection->user);
631  }
632  }
633  else if(session->state == SHELL_SERVER_SESSION_STATE_EXEC)
634  {
635  //Properly terminate the command line with a NULL character
636  session->buffer[session->bufferLen] = '\0';
637 
638  //Process command line
639  error = shellServerProcessCommandLine(session, session->buffer);
640  }
641  else
642  {
643  //Just for sanity
644  }
645 
646  //Close SSH channel
647  sshCloseChannel(channel);
648 
649  //Mark the current session as closed
650  session->state = SHELL_SERVER_SESSION_STATE_CLOSED;
651 
652  //Debug message
653  TRACE_INFO("Shell session terminated...\r\n");
654  }
655 }
656 
657 #endif
error_t shellServerClearHistory(ShellServerSession *session)
Clear command history.
Definition: shell_server.c:498
OsTaskId osCreateTask(const char_t *name, OsTaskCode taskCode, void *arg, const OsTaskParameters *params)
Create a task.
uint_t eventMask
Requested events.
Definition: ssh.h:1581
@ ERROR_NOT_IMPLEMENTED
Definition: error.h:66
#define SHELL_SERVER_MAX_SESSIONS
Definition: shell_server.h:58
SshServerContext * sshServerContext
SSH server context.
Definition: shell_server.h:184
void shellServerTask(void *param)
Shell server task.
Definition: shell_server.c:523
error_t sshSetExitStatus(SshChannel *channel, int32_t exitStatus)
Set exit status.
Definition: ssh.c:2491
uint8_t data[]
Definition: ethernet.h:224
#define OS_INVALID_TASK_ID
error_t sshCloseChannel(SshChannel *channel)
Close channel.
Definition: ssh.c:2511
@ ERROR_OUT_OF_RESOURCES
Definition: error.h:64
#define SHELL_SERVER_HISTORY_SIZE
Definition: shell_server.h:86
uint_t eventFlags
Returned events.
Definition: ssh.h:1582
error_t sshServerRegisterChannelRequestCallback(SshServerContext *context, SshChannelReqCallback callback, void *param)
Register channel request callback function.
Definition: ssh_server.c:376
error_t shellServerSaveHistory(ShellServerSession *session, char_t *history, size_t size, size_t *length)
Save command history.
Definition: shell_server.c:394
error_t shellServerSetExitStatus(ShellServerSession *session, int32_t exitStatus)
Set exit status.
Definition: shell_server.c:363
SshChannel * channel
Handle to a channel to monitor.
Definition: ssh.h:1580
ShellServerCloseCallback closeCallback
Session closing callback function.
Definition: shell_server.h:189
error_t shellServerSetBanner(ShellServerSession *session, const char_t *banner)
Set welcome banner.
Definition: shell_server.c:208
#define osStrlen(s)
Definition: os_port.h:168
OsTaskParameters task[SHELL_SERVER_MAX_SESSIONS]
Task parameters.
Definition: shell_server.h:183
error_t shellServerInit(ShellServerContext *context, const ShellServerSettings *settings)
Initialize shell server context.
Definition: shell_server.c:86
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:2206
Helper functions for SSH secure shell server.
error_t shellServerSetTimeout(ShellServerSession *session, systime_t timeout)
Set timeout for read/write operations.
Definition: shell_server.c:271
SSH secure shell server.
@ SHELL_SERVER_SESSION_STATE_CLOSED
Definition: shell_server.h:146
@ ERROR_INVALID_PARAMETER
Invalid parameter.
Definition: error.h:47
Pseudo-terminal emulation.
#define osMemcpy(dest, src, length)
Definition: os_port.h:144
error_t
Error codes.
Definition: error.h:43
ShellServerCommandLineCallback commandLineCallback
Command line processing callback function.
Definition: shell_server.h:188
void(* OsTaskCode)(void *arg)
Task routine.
uint_t numSessions
Maximum number of shell sessions.
Definition: shell_server.h:185
#define SHELL_SERVER_BUFFER_SIZE
Definition: shell_server.h:72
error_t shellServerStart(ShellServerContext *context)
Start shell server.
Definition: shell_server.c:157
Shell server settings.
Definition: shell_server.h:182
@ ERROR_INVALID_LENGTH
Definition: error.h:111
const OsTaskParameters OS_TASK_DEFAULT_PARAMS
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:2077
#define SSH_SERVER_STACK_SIZE
Definition: ssh_server.h:39
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:2402
#define TRACE_INFO(...)
Definition: debug.h:105
uint8_t length
Definition: tcp.h:375
#define SHELL_SERVER_MAX_PROMPT_LEN
Definition: shell_server.h:93
@ SHELL_SERVER_SESSION_STATE_OPEN
Definition: shell_server.h:148
#define osEnterTask()
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
@ SHELL_SERVER_SESSION_STATE_EXEC
Definition: shell_server.h:149
error_t shellServerChannelRequestCallback(SshChannel *channel, const SshString *type, const uint8_t *data, size_t length, void *param)
SSH channel request callback.
uint32_t systime_t
System time.
#define SSH_SERVER_PRIORITY
Definition: ssh_server.h:46
@ ERROR_TIMEOUT
Definition: error.h:95
char char_t
Definition: compiler_port.h:55
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
Structure describing channel events.
Definition: ssh.h:1579
uint8_t n
bool_t osWaitForEvent(OsEvent *event, systime_t timeout)
Wait until the specified event is in the signaled state.
#define SHELL_SERVER_TICK_INTERVAL
Definition: shell_server.h:65
#define ShellServerSession
Definition: shell_server.h:121
@ SSH_CHANNEL_EVENT_RX_READY
Definition: ssh.h:1132
bool_t osCreateEvent(OsEvent *event)
Create an event object.
ShellServerCheckUserCallback checkUserCallback
User verification callback function.
Definition: shell_server.h:187
void shellServerGetDefaultSettings(ShellServerSettings *settings)
Initialize settings with default values.
Definition: shell_server.c:50
#define ShellServerContext
Definition: shell_server.h:117
error_t shellServerRestoreHistory(ShellServerSession *session, const char_t *history, size_t length)
Restore command history.
Definition: shell_server.c:443
uint8_t flags
Definition: tcp.h:358
error_t shellServerProcessWindowResize(ShellServerSession *session)
Process window resize event.
unsigned int uint_t
Definition: compiler_port.h:57
#define osMemset(p, value, length)
Definition: os_port.h:138
ShellServerSession * sessions
Shell sessions.
Definition: shell_server.h:186
error_t sshSetChannelTimeout(SshChannel *channel, systime_t timeout)
Set timeout for read/write operations.
Definition: ssh.c:2053
Secure Shell (SSH)
#define osStrcpy(s1, s2)
Definition: os_port.h:210
error_t shellServerSetPrompt(ShellServerSession *session, const char_t *prompt)
Set shell prompt.
Definition: shell_server.c:243
error_t shellServerProcessChar(ShellServerSession *session)
Process received character.
@ ERROR_ALREADY_RUNNING
Definition: error.h:294
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.
error_t shellServerProcessCommandLine(ShellServerSession *session, char_t *commandLine)
Command line processing.
#define SshChannel
Definition: ssh.h:900
#define INFINITE_DELAY
Definition: os_port.h:75