scp_client_misc.c
Go to the documentation of this file.
1 /**
2  * @file scp_client_misc.c
3  * @brief Helper functions for SCP client
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.6
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL SCP_TRACE_LEVEL
33 
34 //Dependencies
35 #include "ssh/ssh.h"
36 #include "ssh/ssh_connection.h"
37 #include "ssh/ssh_request.h"
38 #include "ssh/ssh_misc.h"
39 #include "scp/scp_client.h"
40 #include "scp/scp_client_misc.h"
41 #include "debug.h"
42 
43 //Check SSH stack configuration
44 #if (SCP_CLIENT_SUPPORT == ENABLED)
45 
46 
47 /**
48  * @brief Update SCP client state
49  * @param[in] context Pointer to the SCP client context
50  * @param[in] newState New state to switch to
51  **/
52 
54  ScpClientState newState)
55 {
56  //Switch to the new state
57  context->state = newState;
58 
59  //Save current time
60  context->timestamp = osGetSystemTime();
61 }
62 
63 
64 /**
65  * @brief SSH channel request callback
66  * @param[in] channel Handle referencing an SSH channel
67  * @param[in] type Request type
68  * @param[in] data Request-specific data
69  * @param[in] length Length of the request-specific data, in bytes
70  * @param[in] param Pointer to the shell client context
71  * @return Error code
72  **/
73 
75  const SshString *type, const uint8_t *data, size_t length, void *param)
76 {
77  error_t error;
78  ScpClientContext *context;
79 
80  //Debug message
81  TRACE_INFO("SCP client: SSH channel request callback...\r\n");
82 
83  //Point to the SCP client context
84  context = (ScpClientContext *) param;
85 
86  //Check request type
87  if(sshCompareString(type, "exit-status"))
88  {
89  SshExitStatusParams requestParams;
90 
91  //When the command running at the other end terminates, a message can be
92  //sent to return the exit status of the command (refer to RFC 4254,
93  //section 6.10)
94  error = sshParseExitStatusParams(data, length, &requestParams);
95 
96  //Check status code
97  if(!error)
98  {
99  //Matching channel?
100  if(channel == &context->sshChannel)
101  {
102  //Save exit status
103  context->exitStatus = requestParams.exitStatus;
104  }
105  else
106  {
107  //Unknown channel
108  error = ERROR_UNKNOWN_REQUEST;
109  }
110  }
111  }
112  else
113  {
114  //The request is not supported
115  error = ERROR_UNKNOWN_REQUEST;
116  }
117 
118  //Return status code
119  return error;
120 }
121 
122 
123 /**
124  * @brief Open SSH connection
125  * @param[in] context Pointer to the SCP client context
126  * @return Error code
127  **/
128 
130 {
131  error_t error;
132  Socket *socket;
133  SshConnection *connection;
134 
135  //Initialize SSH context
136  error = sshInit(&context->sshContext, &context->sshConnection, 1,
137  &context->sshChannel, 1);
138  //Any error to report?
139  if(error)
140  return error;
141 
142  //Select client operation mode
143  error = sshSetOperationMode(&context->sshContext, SSH_OPERATION_MODE_CLIENT);
144  //Any error to report?
145  if(error)
146  return error;
147 
148  //Register channel request processing callback
149  error = sshRegisterChannelRequestCallback(&context->sshContext,
151  //Any error to report?
152  if(error)
153  return error;
154 
155  //Invoke user-defined callback, if any
156  if(context->sshInitCallback != NULL)
157  {
158  //Perform SSH related initialization
159  error = context->sshInitCallback(context, &context->sshContext);
160  //Any error to report?
161  if(error)
162  return error;
163  }
164 
165  //Open a TCP socket
166  socket = socketOpenEx(context->netContext, SOCKET_TYPE_STREAM,
168 
169  //Valid socket handle
170  if(socket != NULL)
171  {
172  //Associate the socket with the relevant interface
173  socketBindToInterface(socket, context->interface);
174  //Set timeout
175  socketSetTimeout(socket, context->timeout);
176 
177  //Open a new SSH connection
178  connection = sshOpenConnection(&context->sshContext, socket);
179 
180  //Failed to open connection?
181  if(connection == NULL)
182  {
183  //Clean up side effects
185  //Report an error
186  error = ERROR_OPEN_FAILED;
187  }
188  }
189  else
190  {
191  //Failed to open socket
192  error = ERROR_OPEN_FAILED;
193  }
194 
195  //Return status code
196  return error;
197 }
198 
199 
200 /**
201  * @brief Establish SSH connection
202  * @param[in] context Pointer to the SCP client context
203  * @return Error code
204  **/
205 
207 {
208  error_t error;
209 
210  //Check the state of the SSH connection
211  if(context->sshConnection.state < SSH_CONN_STATE_OPEN)
212  {
213  //Perform SSH key exchange and user authentication
214  error = scpClientProcessEvents(context);
215  }
216  else if(context->sshConnection.state == SSH_CONN_STATE_OPEN)
217  {
218  //The SSH connection is established
220  //Successful processing
221  error = NO_ERROR;
222  }
223  else
224  {
225  //Invalid state
226  error = ERROR_WRONG_STATE;
227  }
228 
229  //Return status code
230  return error;
231 }
232 
233 
234 /**
235  * @brief Close SSH connection
236  * @param[in] context Pointer to the SCP client context
237  **/
238 
240 {
241  //Check the state of the SSH connection
242  if(context->sshConnection.state != SSH_CONN_STATE_CLOSED)
243  {
244  //Close SSH connection
245  sshCloseConnection(&context->sshConnection);
246  }
247 
248  //Release SSH context
249  sshDeinit(&context->sshContext);
250 }
251 
252 
253 /**
254  * @brief Send a SCP directive to the server
255  * @param[in] context Pointer to the SCP client context
256  * @param[in] directive SCP directive parameters
257  * @return Error code
258  **/
259 
261  const ScpDirective *directive)
262 {
263  error_t error;
264  size_t n;
265 
266  //Initialize status code
267  error = NO_ERROR;
268 
269  //Format and and send status message
270  while(!error)
271  {
272  //Manage message transmission
273  if(context->bufferLen == 0)
274  {
275  //Format directive line
276  n = scpFormatDirective(directive, context->buffer);
277 
278  //Save the length of the directive line
279  context->bufferLen = n;
280  context->bufferPos = 0;
281  }
282  else if(context->bufferPos < context->bufferLen)
283  {
284  //Send more data
285  error = sshWriteChannel(&context->sshChannel,
286  context->buffer + context->bufferPos,
287  context->bufferLen - context->bufferPos, &n, 0);
288 
289  //Check status code
290  if(error == NO_ERROR || error == ERROR_TIMEOUT)
291  {
292  //Advance data pointer
293  context->bufferPos += n;
294  }
295  }
296  else
297  {
298  //Flush transmit buffer
299  context->bufferLen = 0;
300  context->bufferPos = 0;
301 
302  //We are done
303  break;
304  }
305 
306  //Check status code
307  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
308  {
309  //Process SSH connection events
310  error = scpClientProcessEvents(context);
311  }
312  }
313 
314  //Return status code
315  return error;
316 }
317 
318 
319 /**
320  * @brief Receive a SCP directive from the server
321  * @param[in] context Pointer to the SCP client context
322  * @param[in] directive SCP directive parameters
323  * @return Error code
324  **/
325 
327  ScpDirective *directive)
328 {
329  error_t error;
330  size_t n;
331  uint8_t opcode;
332 
333  //Initialize status code
334  error = NO_ERROR;
335 
336  //Receive and parse SCP directive
337  while(!error)
338  {
339  //Manage message reception
340  if(context->bufferLen == 0)
341  {
342  //Read the directive opcode
343  error = sshReadChannel(&context->sshChannel, context->buffer, 1,
344  &n, 0);
345 
346  //Check status code
347  if(!error)
348  {
349  //Adjust the length of the buffer
350  context->bufferLen += n;
351  }
352  }
353  else if(context->bufferLen < SCP_CLIENT_BUFFER_SIZE)
354  {
355  //Retrieve directive opcode
356  opcode = context->buffer[0];
357 
358  //Check directive opcode
359  if(opcode == SCP_OPCODE_OK ||
361  {
362  //Parse the received directive
363  error = scpParseDirective(context->buffer, directive);
364 
365  //Flush receive buffer
366  context->bufferLen = 0;
367  context->bufferPos = 0;
368 
369  //We are done
370  break;
371  }
372  else if(opcode == SCP_OPCODE_WARNING ||
374  opcode == SCP_OPCODE_FILE ||
375  opcode == SCP_OPCODE_DIR ||
377  {
378  //Limit the number of bytes to read at a time
379  n = SCP_CLIENT_BUFFER_SIZE - context->bufferLen;
380 
381  //Read more data
382  error = sshReadChannel(&context->sshChannel, context->buffer +
383  context->bufferLen, n, &n, SSH_FLAG_BREAK_CRLF);
384 
385  //Check status code
386  if(!error)
387  {
388  //Adjust the length of the buffer
389  context->bufferLen += n;
390 
391  //Check whether the string is properly terminated
392  if(context->bufferLen > 0 &&
393  context->buffer[context->bufferLen - 1] == '\n')
394  {
395  //Properly terminate the string with a NULL character
396  context->buffer[context->bufferLen - 1] = '\0';
397 
398  //Parse the received directive
399  error = scpParseDirective(context->buffer, directive);
400 
401  //Flush receive buffer
402  context->bufferLen = 0;
403  context->bufferPos = 0;
404 
405  //We are done
406  break;
407  }
408  else
409  {
410  //Wait for a new line character
411  error = ERROR_WOULD_BLOCK;
412  }
413  }
414  }
415  else
416  {
417  //Unknown directive
418  error = ERROR_INVALID_COMMAND;
419  }
420  }
421  else
422  {
423  //The implementation limits the size of messages it accepts
424  error = ERROR_BUFFER_OVERFLOW;
425  }
426 
427  //Check status code
428  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
429  {
430  //Process SSH connection events
431  error = scpClientProcessEvents(context);
432  }
433  }
434 
435  //Return status code
436  return error;
437 }
438 
439 
440 /**
441  * @brief Process SCP client events
442  * @param[in] context Pointer to the SCP client context
443  * @return Error code
444  **/
445 
447 {
448  error_t error;
449  uint_t i;
450  SshContext *sshContext;
451  SshConnection *connection;
452 
453  //Point to the SSH context
454  sshContext = &context->sshContext;
455 
456  //Clear event descriptor set
457  osMemset(sshContext->eventDesc, 0, sizeof(sshContext->eventDesc));
458 
459  //Specify the events the application is interested in
460  for(i = 0; i < sshContext->numConnections; i++)
461  {
462  //Point to the structure describing the current connection
463  connection = &sshContext->connections[i];
464 
465  //Loop through active connections only
466  if(connection->state != SSH_CONN_STATE_CLOSED)
467  {
468  //Register the events related to the current SSH connection
469  sshRegisterConnectionEvents(sshContext, connection, &sshContext->eventDesc[i]);
470  }
471  }
472 
473  //Wait for one of the set of sockets to become ready to perform I/O
474  error = socketPoll(sshContext->eventDesc, sshContext->numConnections,
475  &sshContext->event, context->timeout);
476 
477  //Verify status code
478  if(error == NO_ERROR || error == ERROR_WAIT_CANCELED)
479  {
480  //Clear status code
481  error = NO_ERROR;
482 
483  //Event-driven processing
484  for(i = 0; i < sshContext->numConnections && !error; i++)
485  {
486  //Point to the structure describing the current connection
487  connection = &sshContext->connections[i];
488 
489  //Loop through active connections only
490  if(connection->state != SSH_CONN_STATE_CLOSED)
491  {
492  //Check whether the socket is ready to perform I/O
493  if(sshContext->eventDesc[i].eventFlags != 0)
494  {
495  //Connection event handler
496  error = sshProcessConnectionEvents(sshContext, connection);
497  }
498  }
499  }
500  }
501 
502  //Check status code
503  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
504  {
505  //Check whether the timeout has elapsed
506  error = scpClientCheckTimeout(context);
507  }
508 
509  //Return status code
510  return error;
511 }
512 
513 
514 /**
515  * @brief Determine whether a timeout error has occurred
516  * @param[in] context Pointer to the SCP client context
517  * @return Error code
518  **/
519 
521 {
522  error_t error;
523  systime_t time;
524 
525  //Get current time
526  time = osGetSystemTime();
527 
528  //Check whether the timeout has elapsed
529  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
530  {
531  //Report a timeout error
532  error = ERROR_TIMEOUT;
533  }
534  else
535  {
536 #if (NET_RTOS_SUPPORT == ENABLED)
537  //Successful operation
538  error = NO_ERROR;
539 #else
540  //The operation would block
541  error = ERROR_WOULD_BLOCK;
542 #endif
543  }
544 
545  //Return status code
546  return error;
547 }
548 
549 #endif
SCP client.
uint8_t opcode
Definition: dns_common.h:193
@ SSH_CONN_STATE_OPEN
Definition: ssh.h:1127
Helper functions for SCP client.
@ ERROR_WOULD_BLOCK
Definition: error.h:96
@ ERROR_BUFFER_OVERFLOW
Definition: error.h:143
SSH connection protocol.
SshConnection * sshOpenConnection(SshContext *context, Socket *socket)
Open a new SSH connection.
Definition: ssh_misc.c:68
uint8_t data[]
Definition: ethernet.h:224
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:2057
error_t sshRegisterChannelRequestCallback(SshContext *context, SshChannelReqCallback callback, void *param)
Register channel request callback function.
Definition: ssh.c:705
error_t scpClientProcessEvents(ScpClientContext *context)
Process SCP client events.
error_t scpClientSendDirective(ScpClientContext *context, const ScpDirective *directive)
Send a SCP directive to the server.
@ SCP_OPCODE_OK
Definition: scp_common.h:63
error_t sshParseExitStatusParams(const uint8_t *p, size_t length, SshExitStatusParams *params)
Parse "exit-status" channel request parameters.
Definition: ssh_request.c:1708
uint8_t type
Definition: coap_common.h:176
@ ERROR_INVALID_COMMAND
Definition: error.h:100
void scpClientChangeState(ScpClientContext *context, ScpClientState newState)
Update SCP client state.
@ SCP_OPCODE_END
Definition: scp_common.h:68
@ SOCKET_TYPE_STREAM
Definition: socket.h:92
#define timeCompare(t1, t2)
Definition: os_port.h:40
@ SCP_CLIENT_STATE_CONNECTED
Definition: scp_client.h:83
bool_t sshCompareString(const SshString *string, const char_t *value)
Compare a binary string against the supplied value.
Definition: ssh_misc.c:1691
@ ERROR_WRONG_STATE
Definition: error.h:210
error_t sshInit(SshContext *context, SshConnection *connections, uint_t numConnections, SshChannel *channels, uint_t numChannels)
SSH context initialization.
Definition: ssh.c:58
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
@ ERROR_OPEN_FAILED
Definition: error.h:75
#define SshContext
Definition: ssh.h:931
error_t
Error codes.
Definition: error.h:43
void sshDeinit(SshContext *context)
Release SSH context.
Definition: ssh.c:2649
SCP directive parameters.
Definition: scp_common.h:78
error_t scpClientOpenConnection(ScpClientContext *context)
Open SSH connection.
int_t socket(int_t family, int_t type, int_t protocol)
Create a socket that is bound to a specific transport service provider.
Definition: bsd_socket.c:65
#define ScpClientContext
Definition: scp_client.h:66
@ SCP_OPCODE_TIME
Definition: scp_common.h:69
@ SSH_OPERATION_MODE_CLIENT
Definition: ssh.h:953
@ ERROR_UNKNOWN_REQUEST
Definition: error.h:278
error_t sshProcessConnectionEvents(SshContext *context, SshConnection *connection)
Connection event handler.
Definition: ssh_misc.c:375
void sshCloseConnection(SshConnection *connection)
Close SSH connection.
Definition: ssh_misc.c:174
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
@ SCP_OPCODE_DIR
Definition: scp_common.h:67
#define TRACE_INFO(...)
Definition: debug.h:105
uint8_t length
Definition: tcp.h:375
String.
Definition: ssh_types.h:56
@ SSH_CONN_STATE_CLOSED
Definition: ssh.h:1096
error_t socketPoll(SocketEventDesc *eventDesc, uint_t size, OsEvent *extEvent, systime_t timeout)
Wait for one of a set of sockets to become ready to perform I/O.
Definition: socket.c:2145
#define socketBindToInterface
Definition: net_legacy.h:193
error_t scpClientChannelRequestCallback(SshChannel *channel, const SshString *type, const uint8_t *data, size_t length, void *param)
SSH channel request callback.
uint32_t systime_t
System time.
"exit-status" channel request parameters
Definition: ssh_request.h:172
@ ERROR_TIMEOUT
Definition: error.h:95
size_t scpFormatDirective(const ScpDirective *directive, char_t *buffer)
Format SCP directive.
Definition: scp_common.c:48
uint32_t time
@ SCP_OPCODE_FILE
Definition: scp_common.h:66
void scpClientCloseConnection(ScpClientContext *context)
Close SSH connection.
uint8_t n
void sshRegisterConnectionEvents(SshContext *context, SshConnection *connection, SocketEventDesc *eventDesc)
Register connection events.
Definition: ssh_misc.c:282
#define SshConnection
Definition: ssh.h:935
Socket * socketOpenEx(NetContext *context, uint_t type, uint_t protocol)
Create a socket.
Definition: socket.c:143
#define Socket
Definition: socket.h:36
@ ERROR_WAIT_CANCELED
Definition: error.h:73
@ SSH_FLAG_BREAK_CRLF
Definition: ssh.h:979
#define SCP_CLIENT_BUFFER_SIZE
Definition: scp_client.h:54
SSH helper functions.
error_t scpParseDirective(const char_t *buffer, ScpDirective *directive)
Parse SCP directive.
Definition: scp_common.c:127
unsigned int uint_t
Definition: compiler_port.h:57
#define osMemset(p, value, length)
Definition: os_port.h:141
error_t sshSetOperationMode(SshContext *context, SshOperationMode mode)
Set operation mode (client or server)
Definition: ssh.c:167
@ SCP_OPCODE_ERROR
Definition: scp_common.h:65
Secure Shell (SSH)
@ SCP_OPCODE_WARNING
Definition: scp_common.h:64
ScpClientState
SCP client state.
Definition: scp_client.h:79
@ SOCKET_IP_PROTO_TCP
Definition: socket.h:107
error_t scpClientCheckTimeout(ScpClientContext *context)
Determine whether a timeout error has occurred.
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:166
Global request and channel request handling.
error_t scpClientReceiveDirective(ScpClientContext *context, ScpDirective *directive)
Receive a SCP directive from the server.
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.
#define SshChannel
Definition: ssh.h:939
error_t scpClientEstablishConnection(ScpClientContext *context)
Establish SSH connection.
systime_t osGetSystemTime(void)
Retrieve system time.