scp_server_misc.c
Go to the documentation of this file.
1 /**
2  * @file scp_server_misc.c
3  * @brief Helper functions for SCP 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.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_request.h"
37 #include "ssh/ssh_misc.h"
38 #include "scp/scp_server.h"
39 #include "scp/scp_server_file.h"
41 #include "scp/scp_server_misc.h"
42 #include "path.h"
43 #include "debug.h"
44 
45 //Check SSH stack configuration
46 #if (SCP_SERVER_SUPPORT == ENABLED)
47 
48 
49 /**
50  * @brief Handle periodic operations
51  * @param[in] context Pointer to the SCP server context
52  **/
53 
55 {
56 }
57 
58 
59 /**
60  * @brief SSH channel request callback
61  * @param[in] channel Handle referencing an SSH channel
62  * @param[in] type Request type
63  * @param[in] data Request-specific data
64  * @param[in] length Length of the request-specific data, in bytes
65  * @param[in] param Pointer to the SCP server context
66  * @return Error code
67  **/
68 
70  const SshString *type, const uint8_t *data, size_t length,
71  void *param)
72 {
73  error_t error;
74  ScpAccessStatus status;
75  ScpServerContext *context;
76  ScpServerSession *session;
77 
78  //Debug message
79  TRACE_INFO("SCP server: SSH channel request callback...\r\n");
80 
81  //Initialize status code
82  error = NO_ERROR;
83 
84  //Point to the SCP server context
85  context = (ScpServerContext *) param;
86 
87  //Check request type
88  if(sshCompareString(type, "exec"))
89  {
90  SshString arg;
91  SshExecParams requestParams;
92 
93  //This message will request that the server start the execution of the
94  //given command
95  error = sshParseExecParams(data, length, &requestParams);
96  //Any error to report?
97  if(error)
98  return error;
99 
100  //Check the first argument of the command line
101  if(sshGetExecArg(&requestParams, 0, &arg) &&
102  sshCompareString(&arg, "scp"))
103  {
104  //Retrieve the SCP session that matches the channel number
105  session = scpServerFindSession(context, channel);
106 
107  //Any active session found?
108  if(session != NULL)
109  {
110  //Only one of the "shell", "exec" and "subsystem" requests can
111  //succeed per channel (refer to RFC 4254, section 6.5)
112  return ERROR_WRONG_STATE;
113  }
114  else
115  {
116  //Open a new SCP session
117  session = scpServerOpenSession(context, channel);
118  //Check whether the session table runs out of resources
119  if(session == NULL)
120  return ERROR_OUT_OF_RESOURCES;
121 
122  //Invoke user-defined callback, if any
123  if(context->checkUserCallback != NULL)
124  {
125  //Check user name
126  status = context->checkUserCallback(session,
127  channel->connection->user);
128 
129  //Access denied?
130  if(status != SCP_ACCESS_ALLOWED)
131  return ERROR_ACCESS_DENIED;
132  }
133 
134  //Force the channel to operate in non-blocking mode
135  error = sshSetChannelTimeout(channel, 0);
136  //Any error to report?
137  if(error)
138  return error;
139 
140  //Parse SCP command line
141  scpServerParseCommandLine(session, &requestParams);
142 
143  //Notify the SCP server that the session is ready
144  osSetEvent(&session->context->event);
145  }
146  }
147  else
148  {
149  //Unknown command
150  return ERROR_UNKNOWN_REQUEST;
151  }
152  }
153  else
154  {
155  //The request is not supported
156  return ERROR_UNKNOWN_REQUEST;
157  }
158 
159  //Successful processing
160  return NO_ERROR;
161 }
162 
163 
164 /**
165  * @brief SCP command line parsing
166  * @param[in] session Handle referencing an SCP session
167  * @param[in] requestParams Pointer to the "exec" request parameters
168  **/
169 
171  const SshExecParams *requestParams)
172 {
173  error_t error;
174  uint_t i;
175  bool_t t;
176  bool_t f;
177  bool_t r;
178  bool_t d;
179  SshString arg;
180  SshString path;
181 
182  //The options inform the direction of the copy
183  t = FALSE;
184  f = FALSE;
185  r = FALSE;
186  d = FALSE;
187 
188  //Initialize path name
189  path.value = NULL;
190  path.length = 0;
191 
192  //Parse SCP command line
193  for(i = 1; ; i++)
194  {
195  //Get the value of the argument
196  if(sshGetExecArg(requestParams, i, &arg))
197  {
198  //Valid option?
199  if(arg.length > 0 && arg.value[0] == '-')
200  {
201  //The options inform the direction of the copy
202  if(sshCompareString(&arg, "-t"))
203  {
204  //The -t option means copying to a remote machine
205  t = TRUE;
206  }
207  else if(sshCompareString(&arg, "-f"))
208  {
209  //The -f option means copying from a remote machine
210  f = TRUE;
211  }
212  else if(sshCompareString(&arg, "-r"))
213  {
214  //The -r option stands for recursive
215  r = TRUE;
216  }
217  else if(sshCompareString(&arg, "-d"))
218  {
219  //The -d option means that the target should be a directory
220  d = TRUE;
221  }
222  else
223  {
224  //Unknown option
225  }
226  }
227  else
228  {
229  //Point to the first character of the path name
230  path.value = arg.value;
231 
232  //Calculate the length of the path name
233  path.length = requestParams->command.length - (arg.value -
234  requestParams->command.value);
235 
236  //End of command line
237  break;
238  }
239  }
240  else
241  {
242  //End of command line
243  break;
244  }
245  }
246 
247  //Valid path name?
248  if(path.value != NULL && path.length > 0)
249  {
250  //Retrieve the full path name
251  error = scpServerGetPath(session, &path, session->path,
253 
254  //Check status code
255  if(!error)
256  {
257  //Save SCP command options
258  session->recursive = r;
259  session->targetIsDir = d;
260 
261  //Check whether the command line is valid
262  if(t && !f)
263  {
264  //Initiate a write operation
265  session->state = SCP_SERVER_SESSION_STATE_WRITE_INIT;
266  }
267  else if(f && !t)
268  {
269  //Initiate a read operation
270  session->state = SCP_SERVER_SESSION_STATE_READ_INIT;
271  }
272  else
273  {
274  //The command line is not valid
275  error = ERROR_INVALID_COMMAND;
276  }
277  }
278  else
279  {
280  //The path name is too long
281  error = ERROR_INVALID_COMMAND;
282  }
283  }
284  else
285  {
286  //The path name is not valid
287  error = ERROR_INVALID_COMMAND;
288  }
289 
290  //Any error to report?
291  if(error)
292  {
293  //Save status code
294  session->statusCode = error;
295  //Update SCP session state
296  session->state = SCP_SERVER_SESSION_STATE_ERROR;
297  }
298 }
299 
300 
301 /**
302  * @brief Find the SCP session that matches a given SSH channel
303  * @param[in] context Pointer to the SCP server context
304  * @param[in] channel Handle referencing an SSH channel
305  * @return Pointer to the matching SCP session
306  **/
307 
309  SshChannel *channel)
310 {
311  uint_t i;
312  ScpServerSession *session;
313 
314  //Loop through SCP sessions
315  for(i = 0; i < context->numSessions; i++)
316  {
317  //Point to the current session
318  session = &context->sessions[i];
319 
320  //Active session?
321  if(session->state != SCP_SERVER_SESSION_STATE_CLOSED)
322  {
323  //Matching channel found?
324  if(session->channel == channel)
325  {
326  return session;
327  }
328  }
329  }
330 
331  //The channel number does not match any active session
332  return NULL;
333 }
334 
335 
336 /**
337  * @brief Open a new SCP session
338  * @param[in] context Pointer to the SCP server context
339  * @param[in] channel Handle referencing an SSH channel
340  * @return Pointer to the newly created SCP session
341  **/
342 
344  SshChannel *channel)
345 {
346  uint_t i;
347  ScpServerSession *session;
348 
349  //Loop through SCP sessions
350  for(i = 0; i < context->numSessions; i++)
351  {
352  //Point to the current session
353  session = &context->sessions[i];
354 
355  //Check whether the current session is free
356  if(session->state == SCP_SERVER_SESSION_STATE_CLOSED)
357  {
358  //Initialize session parameters
359  osMemset(session, 0, sizeof(ScpServerSession));
360 
361  //Attach SCP server context
362  session->context = context;
363  //Attach SSH channel
364  session->channel = channel;
365 
366  //Set default user's root directory
367  pathCopy(session->rootDir, context->rootDir,
369 
370  //Set default user's home directory
371  pathCopy(session->homeDir, context->rootDir,
373 
374  //Return session handle
375  return session;
376  }
377  }
378 
379  //The session table runs out of space
380  return NULL;
381 }
382 
383 
384 /**
385  * @brief Close an SCP session
386  * @param[in] session Handle referencing an SCP session
387  **/
388 
390 {
391  uint_t i;
392 
393  //Debug message
394  TRACE_INFO("Closing SCP session...\r\n");
395 
396  //Close file
397  if(session->file != NULL)
398  {
399  fsCloseFile(session->file);
400  session->file = NULL;
401  }
402 
403  //Loop through open directories
404  for(i = 0; i < SCP_SERVER_MAX_RECURSION_LEVEL; i++)
405  {
406  //Close directory
407  if(session->dir[i] != NULL)
408  {
409  fsCloseDir(session->dir[i]);
410  session->dir[i] = NULL;
411  }
412  }
413 
414  //Set the exit status of the SCP command
415  if(session->statusCode == NO_ERROR)
416  {
417  sshSetExitStatus(session->channel, 0);
418  }
419  else
420  {
421  sshSetExitStatus(session->channel, 1);
422  }
423 
424  //Close SSH channel
425  sshCloseChannel(session->channel);
426  session->channel = NULL;
427 
428  //Mark the current session as closed
429  session->state = SCP_SERVER_SESSION_STATE_CLOSED;
430 }
431 
432 
433 /**
434  * @brief Register session events
435  * @param[in] session Handle referencing an SCP session
436  * @param[in] eventDesc SSH channel events to be registered
437  **/
438 
440  SshChannelEventDesc *eventDesc)
441 {
442  //Check the state of the SCP session
443  if(session->state == SCP_SERVER_SESSION_STATE_WRITE_INIT ||
444  session->state == SCP_SERVER_SESSION_STATE_WRITE_ACK ||
445  session->state == SCP_SERVER_SESSION_STATE_WRITE_FIN ||
446  session->state == SCP_SERVER_SESSION_STATE_READ_COMMAND ||
447  session->state == SCP_SERVER_SESSION_STATE_READ_STATUS ||
448  session->state == SCP_SERVER_SESSION_STATE_ERROR)
449  {
450  //Wait for the channel to be writable
451  eventDesc->channel = session->channel;
453  }
454  else if(session->state == SCP_SERVER_SESSION_STATE_WRITE_COMMAND ||
455  session->state == SCP_SERVER_SESSION_STATE_WRITE_STATUS ||
456  session->state == SCP_SERVER_SESSION_STATE_READ_INIT ||
457  session->state == SCP_SERVER_SESSION_STATE_READ_ACK ||
458  session->state == SCP_SERVER_SESSION_STATE_READ_FIN)
459  {
460  //Wait for the channel to be readable
461  eventDesc->channel = session->channel;
463  }
464  else if(session->state == SCP_SERVER_SESSION_STATE_WRITE_DATA)
465  {
466  //Any data left to read?
467  if(session->bufferPos < session->bufferLen)
468  {
469  //Wait for the channel to be readable
470  eventDesc->channel = session->channel;
472  }
473  else
474  {
475  //The read operation is complete
477  }
478  }
479  else if(session->state == SCP_SERVER_SESSION_STATE_READ_DATA)
480  {
481  //Any data left to write?
482  if(session->bufferPos < session->bufferLen)
483  {
484  //Wait for the channel to be writable
485  eventDesc->channel = session->channel;
487  }
488  else
489  {
490  //The write operation is complete
492  }
493  }
494  else if(session->state == SCP_SERVER_SESSION_STATE_CLOSING)
495  {
496  //Close SCP session immediately
498  }
499  else
500  {
501  //Just for sanity
502  }
503 }
504 
505 
506 /**
507  * @brief Session event handler
508  * @param[in] session Handle referencing an SCP session
509  **/
510 
512 {
513  error_t error;
514  ScpDirective directive;
515 
516  //Initialize status code
517  error = NO_ERROR;
518 
519  //Check the state of the SCP session
520  if(session->state == SCP_SERVER_SESSION_STATE_WRITE_INIT)
521  {
522  //This status directive indicates a success
523  directive.opcode = SCP_OPCODE_OK;
524  //Send the directive to the SCP client
525  error = scpServerSendDirective(session, &directive);
526 
527  //Check status code
528  if(!error)
529  {
530  //Update SCP session state
532  }
533  }
534  else if(session->state == SCP_SERVER_SESSION_STATE_WRITE_COMMAND)
535  {
536  //Wait for a command from the SCP client
537  error = scpServerReceiveDirective(session, &directive);
538 
539  //Check status code
540  if(!error)
541  {
542  //The source side feeds the commands and the target side consumes them
543  scpServerProcessDirective(session, &directive);
544  }
545  }
546  else if(session->state == SCP_SERVER_SESSION_STATE_WRITE_ACK)
547  {
548  //This status directive indicates a success
549  directive.opcode = SCP_OPCODE_OK;
550  //Send the directive to the SCP client
551  error = scpServerSendDirective(session, &directive);
552 
553  //Check status code
554  if(!error)
555  {
556  //Transfer the contents of the file
557  session->state = SCP_SERVER_SESSION_STATE_WRITE_DATA;
558  }
559  }
560  else if(session->state == SCP_SERVER_SESSION_STATE_WRITE_DATA)
561  {
562  //Write data to the specified file
563  error = scpServerWriteData(session);
564  }
565  else if(session->state == SCP_SERVER_SESSION_STATE_WRITE_STATUS)
566  {
567  //Wait for a status directive from the SCP client
568  error = scpServerReceiveDirective(session, &directive);
569 
570  //Check status code
571  if(!error)
572  {
573  //Check directive opcode
574  if(directive.opcode == SCP_OPCODE_OK)
575  {
576  //A success directive has been received
577  session->state = SCP_SERVER_SESSION_STATE_WRITE_FIN;
578  }
579  else
580  {
581  //A warning or error directive has been received
582  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
583  }
584  }
585  }
586  else if(session->state == SCP_SERVER_SESSION_STATE_WRITE_FIN)
587  {
588  //This status directive indicates a success
589  directive.opcode = SCP_OPCODE_OK;
590  //Send the directive to the SCP client
591  error = scpServerSendDirective(session, &directive);
592 
593  //Check status code
594  if(!error)
595  {
596  //Recursive copy?
597  if(session->recursive || session->targetIsDir)
598  {
599  //Multiple files can be transferred by the client
601  }
602  else
603  {
604  //A single file is transferred by the client
605  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
606  }
607  }
608  }
609  else if(session->state == SCP_SERVER_SESSION_STATE_READ_INIT)
610  {
611  //Wait for a status directive from the SCP client
612  error = scpServerReceiveDirective(session, &directive);
613 
614  //Check status code
615  if(!error)
616  {
617  //Check directive opcode
618  if(directive.opcode == SCP_OPCODE_OK)
619  {
620  //Recursive copy?
621  if(session->recursive)
622  {
623  //Open the specified directory
624  error = scpServerOpenDir(session);
625  }
626  else
627  {
628  //Open the specified file for reading
629  error = scpServerOpenFileForReading(session);
630  }
631 
632  //Check status code
633  if(!error)
634  {
635  //Update SCP session state
636  session->state = SCP_SERVER_SESSION_STATE_READ_COMMAND;
637  }
638  else
639  {
640  //Save status code
641  session->statusCode = error;
642  //Send a status directive to indicate an error
643  session->state = SCP_SERVER_SESSION_STATE_ERROR;
644  //Catch exception
645  error = NO_ERROR;
646  }
647  }
648  else
649  {
650  //A warning or an error message has been received
651  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
652  }
653  }
654  }
655  else if(session->state == SCP_SERVER_SESSION_STATE_READ_COMMAND)
656  {
657  //Format command
658  if(session->file != NULL)
659  {
660  //The 'C' directive indicates the next file to be transferred
661  directive.opcode = SCP_OPCODE_FILE;
662  directive.filename = pathGetFilename(session->path);
663  directive.mode = session->fileMode;
664  directive.size = session->fileSize;
665  }
666  else if(session->dir[session->dirLevel] != NULL)
667  {
668  //The 'D' directive indicates a directory change
669  directive.opcode = SCP_OPCODE_DIR;
670  directive.filename = pathGetFilename(session->path);
671  directive.mode = session->fileMode;
672  directive.size = 0;
673  }
674  else
675  {
676  //The 'E' directive indicates the end of the directory
677  directive.opcode = SCP_OPCODE_END;
678  }
679 
680  //Send the command to the SCP client
681  error = scpServerSendDirective(session, &directive);
682 
683  //Check status code
684  if(!error)
685  {
686  //Update SCP server state
687  session->state = SCP_SERVER_SESSION_STATE_READ_ACK;
688  }
689  }
690  else if(session->state == SCP_SERVER_SESSION_STATE_READ_ACK)
691  {
692  //Wait for a status directive from the SCP client
693  error = scpServerReceiveDirective(session, &directive);
694 
695  //Check status code
696  if(!error)
697  {
698  //Check directive opcode
699  if(directive.opcode == SCP_OPCODE_OK)
700  {
701  if(session->file != NULL)
702  {
703  //Transfer the contents of the file
704  session->state = SCP_SERVER_SESSION_STATE_READ_DATA;
705  }
706  else if(session->dir[session->dirLevel] != NULL)
707  {
708  //Fetch the next entry from the directory
709  scpServerGetNextDirEntry(session);
710  }
711  else
712  {
713  //Change to the parent directory
714  if(session->dirLevel > 0)
715  {
716  session->dirLevel--;
717  }
718 
719  //Valid directory pointer?
720  if(session->dir[session->dirLevel] != NULL)
721  {
722  //Fetch the next entry from the directory
723  scpServerGetNextDirEntry(session);
724  }
725  else
726  {
727  //The copy operation is complete
728  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
729  }
730  }
731  }
732  else
733  {
734  //A warning or an error message has been received
735  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
736  }
737  }
738  }
739  else if(session->state == SCP_SERVER_SESSION_STATE_READ_DATA)
740  {
741  //Read data from the specified file
742  error = scpServerReadData(session);
743  }
744  else if(session->state == SCP_SERVER_SESSION_STATE_READ_STATUS)
745  {
746  //This status directive indicates a success
747  directive.opcode = SCP_OPCODE_OK;
748  //Send the directive to the SCP client
749  error = scpServerSendDirective(session, &directive);
750 
751  //Check status code
752  if(!error)
753  {
754  //Update SCP session state
755  session->state = SCP_SERVER_SESSION_STATE_READ_FIN;
756  }
757  }
758  else if(session->state == SCP_SERVER_SESSION_STATE_READ_FIN)
759  {
760  //Wait for a status directive from the SCP client
761  error = scpServerReceiveDirective(session, &directive);
762 
763  //Check status code
764  if(!error)
765  {
766  //Recursive copy?
767  if(session->recursive)
768  {
769  //Fetch the next entry from the directory
770  scpServerGetNextDirEntry(session);
771  }
772  else
773  {
774  //Update SCP session state
775  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
776  }
777  }
778  }
779  else if(session->state == SCP_SERVER_SESSION_STATE_ERROR)
780  {
781  //This status directive indicates an error
782  directive.opcode = SCP_OPCODE_ERROR;
783 
784  //Warning and error directives can be followed by a textual description
785  if(session->statusCode == ERROR_INVALID_COMMAND)
786  {
787  directive.message = "Invalid command";
788  }
789  else if(session->statusCode == ERROR_INVALID_PATH)
790  {
791  directive.message = "Invalid path";
792  }
793  else if(session->statusCode == ERROR_FILE_NOT_FOUND)
794  {
795  directive.message = "No such file";
796  }
797  else if(session->statusCode == ERROR_DIRECTORY_NOT_FOUND)
798  {
799  directive.message = "No such directory";
800  }
801  else if(session->statusCode == ERROR_ACCESS_DENIED)
802  {
803  directive.message = "Access denied";
804  }
805  else
806  {
807  directive.message = "Protocol error";
808  }
809 
810  //Send the directive to the SCP client
811  error = scpServerSendDirective(session, &directive);
812 
813  //Check status code
814  if(!error)
815  {
816  //Update SCP session state
817  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
818  }
819  }
820  else if(session->state == SCP_SERVER_SESSION_STATE_CLOSING)
821  {
822  //Close SCP session
823  scpServerCloseSession(session);
824  }
825  else
826  {
827  //Invalid state
828  error = ERROR_WRONG_STATE;
829  }
830 
831  //Any communication error?
832  if(error != NO_ERROR && error != ERROR_WOULD_BLOCK && error != ERROR_TIMEOUT)
833  {
834  //Close the SSH connection
835  scpServerCloseSession(session);
836  }
837 }
838 
839 
840 /**
841  * @brief Send a SCP directive to the client
842  * @param[in] session Handle referencing an SCP session
843  * @param[in] directive SCP directive parameters
844  * @return Error code
845  **/
846 
848  const ScpDirective *directive)
849 {
850  error_t error;
851  size_t n;
852 
853  //Initialize status code
854  error = NO_ERROR;
855 
856  //Format and and send status message
857  while(!error)
858  {
859  //Manage message transmission
860  if(session->bufferLen == 0)
861  {
862  //Format directive line
863  n = scpFormatDirective(directive, session->buffer);
864 
865  //Save the length of the directive line
866  session->bufferLen = n;
867  session->bufferPos = 0;
868  }
869  else if(session->bufferPos < session->bufferLen)
870  {
871  //Send more data
872  error = sshWriteChannel(session->channel,
873  session->buffer + session->bufferPos,
874  session->bufferLen - session->bufferPos, &n, 0);
875 
876  //Check status code
877  if(error == NO_ERROR || error == ERROR_TIMEOUT)
878  {
879  //Advance data pointer
880  session->bufferPos += n;
881  }
882  }
883  else
884  {
885  //Flush transmit buffer
886  session->bufferLen = 0;
887  session->bufferPos = 0;
888 
889  //We are done
890  break;
891  }
892  }
893 
894  //Return status code
895  return error;
896 }
897 
898 
899 /**
900  * @brief Receive a SCP directive from the client
901  * @param[in] session Handle referencing an SCP session
902  * @param[in] directive SCP directive parameters
903  * @return Error code
904  **/
905 
907  ScpDirective *directive)
908 {
909  error_t error;
910  size_t n;
911  uint8_t opcode;
912 
913  //Initialize status code
914  error = NO_ERROR;
915 
916  //Receive and parse SCP directive
917  while(!error)
918  {
919  //Manage message reception
920  if(session->bufferLen == 0)
921  {
922  //Read the directive opcode
923  error = sshReadChannel(session->channel, session->buffer, 1, &n, 0);
924 
925  //Check status code
926  if(!error)
927  {
928  //Adjust the length of the buffer
929  session->bufferLen += n;
930  }
931  }
932  else if(session->bufferLen < SCP_SERVER_BUFFER_SIZE)
933  {
934  //Retrieve directive opcode
935  opcode = session->buffer[0];
936 
937  //Check directive opcode
938  if(opcode == SCP_OPCODE_OK)
939  {
940  //Parse the received directive
941  error = scpParseDirective(session->buffer, directive);
942 
943  //Flush receive buffer
944  session->bufferLen = 0;
945  session->bufferPos = 0;
946 
947  //We are done
948  break;
949  }
950  else if(opcode == SCP_OPCODE_WARNING ||
952  opcode == SCP_OPCODE_FILE ||
953  opcode == SCP_OPCODE_DIR ||
954  opcode == SCP_OPCODE_END ||
956  {
957  //Limit the number of bytes to read at a time
958  n = SCP_SERVER_BUFFER_SIZE - session->bufferLen;
959 
960  //Read more data
961  error = sshReadChannel(session->channel, session->buffer +
962  session->bufferLen, n, &n, SSH_FLAG_BREAK_CRLF);
963 
964  //Check status code
965  if(!error)
966  {
967  //Adjust the length of the buffer
968  session->bufferLen += n;
969 
970  //Check whether the string is properly terminated
971  if(session->bufferLen > 0 &&
972  session->buffer[session->bufferLen - 1] == '\n')
973  {
974  //Properly terminate the string with a NULL character
975  session->buffer[session->bufferLen - 1] = '\0';
976 
977  //Parse the received directive
978  error = scpParseDirective(session->buffer, directive);
979 
980  //Flush receive buffer
981  session->bufferLen = 0;
982  session->bufferPos = 0;
983 
984  //We are done
985  break;
986  }
987  else
988  {
989  //Wait for a new line character
990  error = ERROR_WOULD_BLOCK;
991  }
992  }
993  }
994  else
995  {
996  //Unknown directive
997  error = ERROR_INVALID_COMMAND;
998  }
999  }
1000  else
1001  {
1002  //The implementation limits the size of messages it accepts
1003  error = ERROR_BUFFER_OVERFLOW;
1004  }
1005  }
1006 
1007  //Return status code
1008  return error;
1009 }
1010 
1011 
1012 /**
1013  * @brief Process SCP directive
1014  * @param[in] session Handle referencing an SCP session
1015  * @param[in] directive SCP directive sent by the client
1016  **/
1017 
1019  const ScpDirective *directive)
1020 {
1021  error_t error;
1022 
1023  //Check directive opcode
1024  if(directive->opcode == SCP_OPCODE_FILE)
1025  {
1026  //The file name must not contain illegal characters
1027  if(osStrcmp(directive->filename, ".") == 0 ||
1028  osStrcmp(directive->filename, "..") == 0 ||
1029  osStrchr(directive->filename, '*') != NULL ||
1030  osStrchr(directive->filename, '/') != NULL ||
1031  osStrchr(directive->filename, '\\') != NULL)
1032  {
1033  //Save status code
1034  session->statusCode = ERROR_INVALID_PATH;
1035  //Send a status directive to indicate an error
1036  session->state = SCP_SERVER_SESSION_STATE_ERROR;
1037  }
1038  else
1039  {
1040  //Open the specified file for reading
1041  error = scpServerOpenFileForWriting(session, directive->filename,
1042  directive->mode, directive->size);
1043 
1044  //Check status code
1045  if(!error)
1046  {
1047  //Initiate data transfer
1048  session->state = SCP_SERVER_SESSION_STATE_WRITE_ACK;
1049  }
1050  else
1051  {
1052  //Save status code
1053  session->statusCode = ERROR_FILE_NOT_FOUND;
1054  //Send a status directive to indicate an error
1055  session->state = SCP_SERVER_SESSION_STATE_ERROR;
1056  }
1057  }
1058  }
1059  else if(directive->opcode == SCP_OPCODE_DIR)
1060  {
1061  //The file name must not contain illegal characters
1062  if(osStrcmp(directive->filename, ".") == 0 ||
1063  osStrcmp(directive->filename, "..") == 0 ||
1064  osStrchr(directive->filename, '*') != NULL ||
1065  osStrchr(directive->filename, '/') != NULL ||
1066  osStrchr(directive->filename, '\\') != NULL)
1067  {
1068  //Save status code
1069  session->statusCode = ERROR_INVALID_PATH;
1070  //Send a status directive to indicate an error
1071  session->state = SCP_SERVER_SESSION_STATE_ERROR;
1072  }
1073  else
1074  {
1075  //If the folder does not exist, then create it
1076  error = scpServerCreateDir(session, directive->filename);
1077 
1078  //Check status code
1079  if(!error)
1080  {
1081  //Wait for the next command
1082  session->state = SCP_SERVER_SESSION_STATE_WRITE_INIT;
1083  }
1084  else
1085  {
1086  //Save status code
1087  session->statusCode = error;
1088  //Send a status directive to indicate an error
1089  session->state = SCP_SERVER_SESSION_STATE_ERROR;
1090  }
1091  }
1092  }
1093  else if(directive->opcode == SCP_OPCODE_END)
1094  {
1095  //Check current level of recursion
1096  if(session->dirLevel > 0)
1097  {
1098  //Change to the parent directory
1099  pathRemoveFilename(session->path);
1100  pathRemoveSlash(session->path);
1101 
1102  //Decrement recursion level
1103  session->dirLevel--;
1104  //Wait for the next command
1105  session->state = SCP_SERVER_SESSION_STATE_WRITE_INIT;
1106  }
1107  else
1108  {
1109  //Report an error
1110  session->statusCode = ERROR_DIRECTORY_NOT_FOUND;
1111  //Send a status directive to indicate an error
1112  session->state = SCP_SERVER_SESSION_STATE_ERROR;
1113  }
1114  }
1115  else if(directive->opcode == SCP_OPCODE_TIME)
1116  {
1117  //Discard time directives
1118  session->state = SCP_SERVER_SESSION_STATE_WRITE_INIT;
1119  }
1120  else
1121  {
1122  //A warning or an error message has been received
1123  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
1124  }
1125 }
1126 
1127 
1128 /**
1129  * @brief Get permissions for the specified file or directory
1130  * @param[in] session Handle referencing an SCP session
1131  * @param[in] path Canonical path of the file
1132  * @return Access rights for the specified file
1133  **/
1134 
1136  const char_t *path)
1137 {
1138  size_t n;
1139  uint_t perm;
1140  ScpServerContext *context;
1141 
1142  //Point to the SCP server context
1143  context = session->context;
1144 
1145  //Calculate the length of the root directory
1146  n = osStrlen(session->rootDir);
1147 
1148  //Make sure the pathname is valid
1149  if(osStrncmp(path, session->rootDir, n) == 0)
1150  {
1151  //Strip root directory from the pathname
1152  path = scpServerStripRootDir(session, path);
1153 
1154  //Invoke user-defined callback, if any
1155  if(context->getFilePermCallback != NULL)
1156  {
1157  //Retrieve access rights for the specified file
1158  perm = context->getFilePermCallback(session,
1159  session->channel->connection->user, path);
1160  }
1161  else
1162  {
1163  //Use default access rights
1166  }
1167  }
1168  else
1169  {
1170  //The specified pathname is not valid
1171  perm = 0;
1172  }
1173 
1174  //Return access rights
1175  return perm;
1176 }
1177 
1178 
1179 /**
1180  * @brief Retrieve the full pathname
1181  * @param[in] session Handle referencing an SCP session
1182  * @param[in] path Relative or absolute path
1183  * @param[out] fullPath Resulting full path
1184  * @param[in] maxLen Maximum acceptable path length
1185  * @return Error code
1186  **/
1187 
1189  char_t *fullPath, size_t maxLen)
1190 {
1191  size_t n;
1192 
1193  //Relative or absolute path?
1194  if(path->length > 0 && (path->value[0] == '/' || path->value[0] == '\\'))
1195  {
1196  //Check the length of the root directory
1197  if(osStrlen(session->rootDir) > maxLen)
1198  return ERROR_FAILURE;
1199 
1200  //Copy the root directory
1201  osStrcpy(fullPath, session->rootDir);
1202  }
1203  else
1204  {
1205  //Check the length of the home directory
1206  if(osStrlen(session->homeDir) > maxLen)
1207  return ERROR_FAILURE;
1208 
1209  //Copy the home directory
1210  osStrcpy(fullPath, session->homeDir);
1211  }
1212 
1213  //Append a slash character to the root directory
1214  if(fullPath[0] != '\0')
1215  {
1216  pathAddSlash(fullPath, maxLen);
1217  }
1218 
1219  //Retrieve the length of the path name
1220  n = osStrlen(fullPath);
1221 
1222  //Check the length of the full path name
1223  if((n + path->length) > maxLen)
1224  return ERROR_FAILURE;
1225 
1226  //Append the specified path
1227  osStrncpy(fullPath + n, path->value, path->length);
1228  //Properly terminate the string with a NULL character
1229  fullPath[n + path->length] = '\0';
1230 
1231  //Clean the resulting path
1232  pathCanonicalize(fullPath);
1233  pathRemoveSlash(fullPath);
1234 
1235  //Calculate the length of the root directory
1236  n = osStrlen(session->rootDir);
1237 
1238  //If the server implementation limits access to certain parts of the file
1239  //system, it must be extra careful in parsing file names when enforcing
1240  //such restrictions
1241  if(osStrncmp(fullPath, session->rootDir, n) != 0)
1242  return ERROR_INVALID_PATH;
1243 
1244  //Successful processing
1245  return NO_ERROR;
1246 }
1247 
1248 
1249 /**
1250  * @brief Strip root directory from specified pathname
1251  * @param[in] session Handle referencing an SCP session
1252  * @param[in] path input pathname
1253  * @return Resulting pathname with root directory stripped
1254  **/
1255 
1257  const char_t *path)
1258 {
1259  //Default directory
1260  static const char_t defaultDir[] = "/";
1261 
1262  //Local variables
1263  size_t m;
1264  size_t n;
1265 
1266  //Retrieve the length of the root directory
1267  n = osStrlen(session->rootDir);
1268  //Retrieve the length of the specified pathname
1269  m = osStrlen(path);
1270 
1271  //Strip the root directory from the specified pathname
1272  if(n <= 1)
1273  {
1274  return path;
1275  }
1276  else if(n < m)
1277  {
1278  return path + n;
1279  }
1280  else
1281  {
1282  return defaultDir;
1283  }
1284 }
1285 
1286 #endif
Path manipulation helper functions.
#define osStrchr(s, c)
Definition: os_port.h:201
uint8_t opcode
Definition: dns_common.h:193
int bool_t
Definition: compiler_port.h:63
void scpServerCloseSession(ScpServerSession *session)
Close an SCP session.
error_t scpServerOpenFileForWriting(ScpServerSession *session, const char_t *filename, uint32_t mode, uint64_t size)
Open a file for writing.
uint_t eventMask
Requested events.
Definition: ssh.h:1622
error_t scpServerCreateDir(ScpServerSession *session, const char_t *name)
Create a directory.
@ ERROR_WOULD_BLOCK
Definition: error.h:96
uint64_t size
Definition: scp_common.h:81
@ SCP_SERVER_SESSION_STATE_WRITE_STATUS
Definition: scp_server.h:165
error_t scpServerSendDirective(ScpServerSession *session, const ScpDirective *directive)
Send a SCP directive to the client.
void scpServerProcessDirective(ScpServerSession *session, const ScpDirective *directive)
Process SCP directive.
@ ERROR_BUFFER_OVERFLOW
Definition: error.h:143
ScpServerSession * scpServerFindSession(ScpServerContext *context, SshChannel *channel)
Find the SCP session that matches a given SSH channel.
Helper functions for SCP server.
uint8_t t
Definition: lldp_ext_med.h:212
error_t sshSetExitStatus(SshChannel *channel, int32_t exitStatus)
Set exit status.
Definition: ssh.c:2491
#define TRUE
Definition: os_port.h:50
uint8_t data[]
Definition: ethernet.h:224
error_t sshCloseChannel(SshChannel *channel)
Close channel.
Definition: ssh.c:2558
@ ERROR_OUT_OF_RESOURCES
Definition: error.h:64
#define SCP_SERVER_BUFFER_SIZE
Definition: scp_server.h:74
uint_t eventFlags
Returned events.
Definition: ssh.h:1623
@ SCP_SERVER_SESSION_STATE_WRITE_COMMAND
Definition: scp_server.h:162
@ SCP_OPCODE_OK
Definition: scp_common.h:63
"exec" channel request parameters
Definition: ssh_request.h:119
uint8_t type
Definition: coap_common.h:176
#define SCP_SERVER_MAX_RECURSION_LEVEL
Definition: scp_server.h:102
@ ERROR_INVALID_COMMAND
Definition: error.h:100
SshChannel * channel
Handle to a channel to monitor.
Definition: ssh.h:1621
size_t length
Definition: ssh_types.h:58
#define osStrcmp(s1, s2)
Definition: os_port.h:177
error_t scpServerOpenFileForReading(ScpServerSession *session)
Open a file for reading.
#define ScpServerSession
Definition: scp_server.h:123
#define osStrlen(s)
Definition: os_port.h:171
@ SCP_SERVER_SESSION_STATE_WRITE_INIT
Definition: scp_server.h:161
@ SCP_OPCODE_END
Definition: scp_common.h:68
void scpServerGetNextDirEntry(ScpServerSession *session)
Fetch the next entry from the directory.
@ SCP_FILE_PERM_READ
Definition: scp_server.h:149
bool_t sshCompareString(const SshString *string, const char_t *value)
Compare a binary string against the supplied value.
Definition: ssh_misc.c:1691
const char_t * pathGetFilename(const char_t *path)
Extract the file name from the supplied path.
Definition: path.c:81
#define SCP_SERVER_MAX_PATH_LEN
Definition: scp_server.h:95
uint8_t r
Definition: ndp.h:346
@ ERROR_WRONG_STATE
Definition: error.h:210
ScpAccessStatus
Access status.
Definition: scp_server.h:136
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
Directory operations.
@ SSH_CHANNEL_EVENT_TX_READY
Definition: ssh.h:1167
error_t sshParseExecParams(const uint8_t *p, size_t length, SshExecParams *params)
Parse "exec" channel request parameters.
Definition: ssh_request.c:1512
void pathCanonicalize(char_t *path)
Simplify a path.
Definition: path.c:162
error_t scpServerWriteData(ScpServerSession *session)
Write data to the specified file.
#define FALSE
Definition: os_port.h:46
@ SCP_SERVER_SESSION_STATE_CLOSING
Definition: scp_server.h:174
@ SCP_SERVER_SESSION_STATE_WRITE_ACK
Definition: scp_server.h:163
const char_t * value
Definition: ssh_types.h:57
error_t scpServerChannelRequestCallback(SshChannel *channel, const SshString *type, const uint8_t *data, size_t length, void *param)
SSH channel request callback.
error_t
Error codes.
Definition: error.h:43
@ SCP_SERVER_SESSION_STATE_READ_STATUS
Definition: scp_server.h:171
error_t scpServerReceiveDirective(ScpServerSession *session, ScpDirective *directive)
Receive a SCP directive from the client.
@ ERROR_FILE_NOT_FOUND
Definition: error.h:157
@ SCP_SERVER_SESSION_STATE_ERROR
Definition: scp_server.h:173
SCP directive parameters.
Definition: scp_common.h:78
void fsCloseFile(FsFile *file)
Close a file.
@ SCP_OPCODE_TIME
Definition: scp_common.h:69
@ ERROR_FAILURE
Generic error code.
Definition: error.h:45
uint32_t mode
Definition: scp_common.h:80
error_t scpServerReadData(ScpServerSession *session)
Read data from the specified file.
void scpServerTick(ScpServerContext *context)
Handle periodic operations.
ScpOpcode opcode
Definition: scp_common.h:79
@ ERROR_UNKNOWN_REQUEST
Definition: error.h:278
error_t scpServerOpenDir(ScpServerSession *session)
Open a directory.
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 ScpServerContext
Definition: scp_server.h:119
@ ERROR_ACCESS_DENIED
Definition: error.h:149
void scpServerParseCommandLine(ScpServerSession *session, const SshExecParams *requestParams)
SCP command line parsing.
#define SCP_SERVER_MAX_ROOT_DIR_LEN
Definition: scp_server.h:81
#define TRACE_INFO(...)
Definition: debug.h:105
uint8_t length
Definition: tcp.h:375
void pathAddSlash(char_t *path, size_t maxLen)
Add a slash to the end of a string.
Definition: path.c:344
SCP server.
void scpServerRegisterSessionEvents(ScpServerSession *session, SshChannelEventDesc *eventDesc)
Register session events.
@ SCP_FILE_PERM_WRITE
Definition: scp_server.h:150
String.
Definition: ssh_types.h:56
File operations.
const char_t * scpServerStripRootDir(ScpServerSession *session, const char_t *path)
Strip root directory from specified pathname.
@ ERROR_INVALID_PATH
Definition: error.h:147
@ SCP_SERVER_SESSION_STATE_WRITE_FIN
Definition: scp_server.h:166
@ ERROR_DIRECTORY_NOT_FOUND
Definition: error.h:165
@ SCP_SERVER_SESSION_STATE_READ_COMMAND
Definition: scp_server.h:168
@ ERROR_TIMEOUT
Definition: error.h:95
char char_t
Definition: compiler_port.h:55
@ SCP_SERVER_SESSION_STATE_CLOSED
Definition: scp_server.h:160
#define SCP_SERVER_MAX_HOME_DIR_LEN
Definition: scp_server.h:88
size_t scpFormatDirective(const ScpDirective *directive, char_t *buffer)
Format SCP directive.
Definition: scp_common.c:48
uint_t scpServerGetFilePermissions(ScpServerSession *session, const char_t *path)
Get permissions for the specified file or directory.
void scpServerProcessSessionEvents(ScpServerSession *session)
Session event handler.
@ SCP_OPCODE_FILE
Definition: scp_common.h:66
Structure describing channel events.
Definition: ssh.h:1620
uint8_t m
Definition: ndp.h:304
uint8_t n
@ SSH_CHANNEL_EVENT_RX_READY
Definition: ssh.h:1171
@ SCP_FILE_PERM_LIST
Definition: scp_server.h:148
const char_t * filename
Definition: scp_common.h:84
const char_t * message
Definition: scp_common.h:85
@ SCP_SERVER_SESSION_STATE_READ_INIT
Definition: scp_server.h:167
ScpServerSession * scpServerOpenSession(ScpServerContext *context, SshChannel *channel)
Open a new SCP session.
error_t scpServerGetPath(ScpServerSession *session, const SshString *path, char_t *fullPath, size_t maxLen)
Retrieve the full pathname.
@ SSH_FLAG_BREAK_CRLF
Definition: ssh.h:979
#define osStrncpy(s1, s2, length)
Definition: os_port.h:219
SSH helper functions.
SshString command
Definition: ssh_request.h:120
error_t scpParseDirective(const char_t *buffer, ScpDirective *directive)
Parse SCP directive.
Definition: scp_common.c:127
void osSetEvent(OsEvent *event)
Set the specified event object to the signaled state.
#define osStrncmp(s1, s2, length)
Definition: os_port.h:183
void pathRemoveSlash(char_t *path)
Remove the trailing slash from a given path.
Definition: path.c:376
@ SCP_SERVER_SESSION_STATE_WRITE_DATA
Definition: scp_server.h:164
void fsCloseDir(FsDir *dir)
Close a directory stream.
unsigned int uint_t
Definition: compiler_port.h:57
#define osMemset(p, value, length)
Definition: os_port.h:141
@ SCP_OPCODE_ERROR
Definition: scp_common.h:65
error_t sshSetChannelTimeout(SshChannel *channel, systime_t timeout)
Set timeout for read/write operations.
Definition: ssh.c:2053
Secure Shell (SSH)
@ SCP_OPCODE_WARNING
Definition: scp_common.h:64
@ SCP_SERVER_SESSION_STATE_READ_ACK
Definition: scp_server.h:169
#define osStrcpy(s1, s2)
Definition: os_port.h:213
void pathRemoveFilename(char_t *path)
Remove the trailing file name from the supplied path.
Definition: path.c:124
Global request and channel request handling.
@ SCP_SERVER_SESSION_STATE_READ_FIN
Definition: scp_server.h:172
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.
@ SCP_SERVER_SESSION_STATE_READ_DATA
Definition: scp_server.h:170
#define SshChannel
Definition: ssh.h:939
void pathCopy(char_t *dest, const char_t *src, size_t maxLen)
Copy a path.
Definition: path.c:141
@ SCP_ACCESS_ALLOWED
Definition: scp_server.h:138
bool_t sshGetExecArg(const SshExecParams *params, uint_t index, SshString *arg)
Retrieve the specified argument from an "exec" request.
Definition: ssh_request.c:1540