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-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.4
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 Open SSH connection
66  * @param[in] context Pointer to the SCP client context
67  * @return Error code
68  **/
69 
71 {
72  error_t error;
73  Socket *socket;
74  SshConnection *connection;
75 
76  //Initialize SSH context
77  error = sshInit(&context->sshContext, &context->sshConnection, 1,
78  &context->sshChannel, 1);
79  //Any error to report?
80  if(error)
81  return error;
82 
83  //Select client operation mode
84  error = sshSetOperationMode(&context->sshContext, SSH_OPERATION_MODE_CLIENT);
85  //Any error to report?
86  if(error)
87  return error;
88 
89  //Invoke user-defined callback, if any
90  if(context->sshInitCallback != NULL)
91  {
92  //Perform SSH related initialization
93  error = context->sshInitCallback(context, &context->sshContext);
94  //Any error to report?
95  if(error)
96  return error;
97  }
98 
99  //Open a TCP socket
101 
102  //Valid socket handle
103  if(socket != NULL)
104  {
105  //Associate the socket with the relevant interface
106  socketBindToInterface(socket, context->interface);
107  //Set timeout
108  socketSetTimeout(socket, context->timeout);
109 
110  //Open a new SSH connection
111  connection = sshOpenConnection(&context->sshContext, socket);
112 
113  //Failed to open connection?
114  if(connection == NULL)
115  {
116  //Clean up side effects
118  //Report an error
119  error = ERROR_OPEN_FAILED;
120  }
121  }
122  else
123  {
124  //Failed to open socket
125  error = ERROR_OPEN_FAILED;
126  }
127 
128  //Return status code
129  return error;
130 }
131 
132 
133 /**
134  * @brief Establish SSH connection
135  * @param[in] context Pointer to the SCP client context
136  * @return Error code
137  **/
138 
140 {
141  error_t error;
142 
143  //Check the state of the SSH connection
144  if(context->sshConnection.state < SSH_CONN_STATE_OPEN)
145  {
146  //Perform SSH key exchange and user authentication
147  error = scpClientProcessEvents(context);
148  }
149  else if(context->sshConnection.state == SSH_CONN_STATE_OPEN)
150  {
151  //The SSH connection is established
153  //Successful processing
154  error = NO_ERROR;
155  }
156  else
157  {
158  //Invalid state
159  error = ERROR_WRONG_STATE;
160  }
161 
162  //Return status code
163  return error;
164 }
165 
166 
167 /**
168  * @brief Close SSH connection
169  * @param[in] context Pointer to the SCP client context
170  **/
171 
173 {
174  //Check the state of the SSH connection
175  if(context->sshConnection.state != SSH_CONN_STATE_CLOSED)
176  {
177  //Close SSH connection
178  sshCloseConnection(&context->sshConnection);
179  }
180 
181  //Release SSH context
182  sshDeinit(&context->sshContext);
183 }
184 
185 
186 /**
187  * @brief Send a SCP directive to the server
188  * @param[in] context Pointer to the SCP client context
189  * @param[in] directive SCP directive parameters
190  * @return Error code
191  **/
192 
194  const ScpDirective *directive)
195 {
196  error_t error;
197  size_t n;
198 
199  //Initialize status code
200  error = NO_ERROR;
201 
202  //Format and and send status message
203  while(!error)
204  {
205  //Manage message transmission
206  if(context->bufferLen == 0)
207  {
208  //Format directive line
209  n = scpFormatDirective(directive, context->buffer);
210 
211  //Save the length of the directive line
212  context->bufferLen = n;
213  context->bufferPos = 0;
214  }
215  else if(context->bufferPos < context->bufferLen)
216  {
217  //Send more data
218  error = sshWriteChannel(&context->sshChannel,
219  context->buffer + context->bufferPos,
220  context->bufferLen - context->bufferPos, &n, 0);
221 
222  //Check status code
223  if(error == NO_ERROR || error == ERROR_TIMEOUT)
224  {
225  //Advance data pointer
226  context->bufferPos += n;
227  }
228  }
229  else
230  {
231  //Flush transmit buffer
232  context->bufferLen = 0;
233  context->bufferPos = 0;
234 
235  //We are done
236  break;
237  }
238 
239  //Check status code
240  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
241  {
242  //Process SSH connection events
243  error = scpClientProcessEvents(context);
244  }
245  }
246 
247  //Return status code
248  return error;
249 }
250 
251 
252 /**
253  * @brief Receive a SCP directive from the server
254  * @param[in] context Pointer to the SCP client context
255  * @param[in] directive SCP directive parameters
256  * @return Error code
257  **/
258 
260  ScpDirective *directive)
261 {
262  error_t error;
263  size_t n;
264  uint8_t opcode;
265 
266  //Initialize status code
267  error = NO_ERROR;
268 
269  //Receive and parse SCP directive
270  while(!error)
271  {
272  //Manage message reception
273  if(context->bufferLen == 0)
274  {
275  //Read the directive opcode
276  error = sshReadChannel(&context->sshChannel, context->buffer, 1,
277  &n, 0);
278 
279  //Check status code
280  if(!error)
281  {
282  //Adjust the length of the buffer
283  context->bufferLen += n;
284  }
285  }
286  else if(context->bufferLen < SCP_CLIENT_BUFFER_SIZE)
287  {
288  //Retrieve directive opcode
289  opcode = context->buffer[0];
290 
291  //Check directive opcode
292  if(opcode == SCP_OPCODE_OK ||
294  {
295  //Parse the received directive
296  error = scpParseDirective(context->buffer, directive);
297 
298  //Flush receive buffer
299  context->bufferLen = 0;
300  context->bufferPos = 0;
301 
302  //We are done
303  break;
304  }
305  else if(opcode == SCP_OPCODE_WARNING ||
307  opcode == SCP_OPCODE_FILE ||
308  opcode == SCP_OPCODE_DIR ||
310  {
311  //Limit the number of bytes to read at a time
312  n = SCP_CLIENT_BUFFER_SIZE - context->bufferLen;
313 
314  //Read more data
315  error = sshReadChannel(&context->sshChannel, context->buffer +
316  context->bufferLen, n, &n, SSH_FLAG_BREAK_CRLF);
317 
318  //Check status code
319  if(!error)
320  {
321  //Adjust the length of the buffer
322  context->bufferLen += n;
323 
324  //Check whether the string is properly terminated
325  if(context->bufferLen > 0 &&
326  context->buffer[context->bufferLen - 1] == '\n')
327  {
328  //Properly terminate the string with a NULL character
329  context->buffer[context->bufferLen - 1] = '\0';
330 
331  //Parse the received directive
332  error = scpParseDirective(context->buffer, directive);
333 
334  //Flush receive buffer
335  context->bufferLen = 0;
336  context->bufferPos = 0;
337 
338  //We are done
339  break;
340  }
341  else
342  {
343  //Wait for a new line character
344  error = ERROR_WOULD_BLOCK;
345  }
346  }
347  }
348  else
349  {
350  //Unknown directive
351  error = ERROR_INVALID_COMMAND;
352  }
353  }
354  else
355  {
356  //The implementation limits the size of messages it accepts
357  error = ERROR_BUFFER_OVERFLOW;
358  }
359 
360  //Check status code
361  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
362  {
363  //Process SSH connection events
364  error = scpClientProcessEvents(context);
365  }
366  }
367 
368  //Return status code
369  return error;
370 }
371 
372 
373 /**
374  * @brief Process SCP client events
375  * @param[in] context Pointer to the SCP client context
376  * @return Error code
377  **/
378 
380 {
381  error_t error;
382  uint_t i;
383  SshContext *sshContext;
384  SshConnection *connection;
385 
386  //Point to the SSH context
387  sshContext = &context->sshContext;
388 
389  //Clear event descriptor set
390  osMemset(sshContext->eventDesc, 0, sizeof(sshContext->eventDesc));
391 
392  //Specify the events the application is interested in
393  for(i = 0; i < sshContext->numConnections; i++)
394  {
395  //Point to the structure describing the current connection
396  connection = &sshContext->connections[i];
397 
398  //Loop through active connections only
399  if(connection->state != SSH_CONN_STATE_CLOSED)
400  {
401  //Register the events related to the current SSH connection
402  sshRegisterConnectionEvents(sshContext, connection, &sshContext->eventDesc[i]);
403  }
404  }
405 
406  //Wait for one of the set of sockets to become ready to perform I/O
407  error = socketPoll(sshContext->eventDesc, sshContext->numConnections,
408  &sshContext->event, context->timeout);
409 
410  //Verify status code
411  if(error == NO_ERROR || error == ERROR_WAIT_CANCELED)
412  {
413  //Clear status code
414  error = NO_ERROR;
415 
416  //Event-driven processing
417  for(i = 0; i < sshContext->numConnections && !error; i++)
418  {
419  //Point to the structure describing the current connection
420  connection = &sshContext->connections[i];
421 
422  //Loop through active connections only
423  if(connection->state != SSH_CONN_STATE_CLOSED)
424  {
425  //Check whether the socket is ready to perform I/O
426  if(sshContext->eventDesc[i].eventFlags != 0)
427  {
428  //Connection event handler
429  error = sshProcessConnectionEvents(sshContext, connection);
430  }
431  }
432  }
433  }
434 
435  //Check status code
436  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
437  {
438  //Check whether the timeout has elapsed
439  error = scpClientCheckTimeout(context);
440  }
441 
442  //Return status code
443  return error;
444 }
445 
446 
447 /**
448  * @brief Determine whether a timeout error has occurred
449  * @param[in] context Pointer to the SCP client context
450  * @return Error code
451  **/
452 
454 {
455  error_t error;
456  systime_t time;
457 
458  //Get current time
459  time = osGetSystemTime();
460 
461  //Check whether the timeout has elapsed
462  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
463  {
464  //Report a timeout error
465  error = ERROR_TIMEOUT;
466  }
467  else
468  {
469 #if (NET_RTOS_SUPPORT == ENABLED)
470  //Successful operation
471  error = NO_ERROR;
472 #else
473  //The operation would block
474  error = ERROR_WOULD_BLOCK;
475 #endif
476  }
477 
478  //Return status code
479  return error;
480 }
481 
482 #endif
SCP client.
uint8_t opcode
Definition: dns_common.h:188
@ SSH_CONN_STATE_OPEN
Definition: ssh.h:1062
Helper functions for SCP client.
@ ERROR_WOULD_BLOCK
Definition: error.h:96
@ ERROR_BUFFER_OVERFLOW
Definition: error.h:142
SSH connection protocol.
SshConnection * sshOpenConnection(SshContext *context, Socket *socket)
Open a new SSH connection.
Definition: ssh_misc.c:66
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:2062
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_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:78
@ ERROR_WRONG_STATE
Definition: error.h:209
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:2180
@ ERROR_OPEN_FAILED
Definition: error.h:75
#define SshContext
Definition: ssh.h:870
error_t
Error codes.
Definition: error.h:43
void sshDeinit(SshContext *context)
Release SSH context.
Definition: ssh.c:2556
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:61
@ SCP_OPCODE_TIME
Definition: scp_common.h:69
@ SSH_OPERATION_MODE_CLIENT
Definition: ssh.h:892
error_t sshProcessConnectionEvents(SshContext *context, SshConnection *connection)
Connection event handler.
Definition: ssh_misc.c:372
void sshCloseConnection(SshConnection *connection)
Close SSH connection.
Definition: ssh_misc.c:172
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
@ SCP_OPCODE_DIR
Definition: scp_common.h:67
Socket * socketOpen(uint_t type, uint_t protocol)
Create a socket (UDP or TCP)
Definition: socket.c:125
@ SSH_CONN_STATE_CLOSED
Definition: ssh.h:1033
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:2149
#define socketBindToInterface
Definition: net_legacy.h:193
uint32_t systime_t
System time.
@ 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:280
#define SshConnection
Definition: ssh.h:874
#define Socket
Definition: socket.h:36
@ ERROR_WAIT_CANCELED
Definition: error.h:73
@ SSH_FLAG_BREAK_CRLF
Definition: ssh.h:918
#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:50
#define osMemset(p, value, length)
Definition: os_port.h:135
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:74
@ 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:148
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.
error_t scpClientEstablishConnection(ScpClientContext *context)
Establish SSH connection.
systime_t osGetSystemTime(void)
Retrieve system time.