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-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_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  //Close SSH channel
415  sshCloseChannel(session->channel);
416  session->channel = NULL;
417 
418  //Mark the current session as closed
419  session->state = SCP_SERVER_SESSION_STATE_CLOSED;
420 }
421 
422 
423 /**
424  * @brief Register session events
425  * @param[in] session Handle referencing an SCP session
426  * @param[in] eventDesc SSH channel events to be registered
427  **/
428 
430  SshChannelEventDesc *eventDesc)
431 {
432  //Check the state of the SCP session
433  if(session->state == SCP_SERVER_SESSION_STATE_WRITE_INIT ||
434  session->state == SCP_SERVER_SESSION_STATE_WRITE_ACK ||
435  session->state == SCP_SERVER_SESSION_STATE_WRITE_FIN ||
436  session->state == SCP_SERVER_SESSION_STATE_READ_COMMAND ||
437  session->state == SCP_SERVER_SESSION_STATE_READ_STATUS ||
438  session->state == SCP_SERVER_SESSION_STATE_ERROR)
439  {
440  //Wait for the channel to be writable
441  eventDesc->channel = session->channel;
443  }
444  else if(session->state == SCP_SERVER_SESSION_STATE_WRITE_COMMAND ||
445  session->state == SCP_SERVER_SESSION_STATE_WRITE_STATUS ||
446  session->state == SCP_SERVER_SESSION_STATE_READ_INIT ||
447  session->state == SCP_SERVER_SESSION_STATE_READ_ACK ||
448  session->state == SCP_SERVER_SESSION_STATE_READ_FIN)
449  {
450  //Wait for the channel to be readable
451  eventDesc->channel = session->channel;
453  }
454  else if(session->state == SCP_SERVER_SESSION_STATE_WRITE_DATA)
455  {
456  //Any data left to read?
457  if(session->bufferPos < session->bufferLen)
458  {
459  //Wait for the channel to be readable
460  eventDesc->channel = session->channel;
462  }
463  else
464  {
465  //The read operation is complete
467  }
468  }
469  else if(session->state == SCP_SERVER_SESSION_STATE_READ_DATA)
470  {
471  //Any data left to write?
472  if(session->bufferPos < session->bufferLen)
473  {
474  //Wait for the channel to be writable
475  eventDesc->channel = session->channel;
477  }
478  else
479  {
480  //The write operation is complete
482  }
483  }
484  else if(session->state == SCP_SERVER_SESSION_STATE_CLOSING)
485  {
486  //Close SCP session immediately
488  }
489  else
490  {
491  //Just for sanity
492  }
493 }
494 
495 
496 /**
497  * @brief Session event handler
498  * @param[in] session Handle referencing an SCP session
499  **/
500 
502 {
503  error_t error;
504  ScpDirective directive;
505 
506  //Initialize status code
507  error = NO_ERROR;
508 
509  //Check the state of the SCP session
510  if(session->state == SCP_SERVER_SESSION_STATE_WRITE_INIT)
511  {
512  //This status directive indicates a success
513  directive.opcode = SCP_OPCODE_OK;
514  //Send the directive to the SCP client
515  error = scpServerSendDirective(session, &directive);
516 
517  //Check status code
518  if(!error)
519  {
520  //Update SCP session state
522  }
523  }
524  else if(session->state == SCP_SERVER_SESSION_STATE_WRITE_COMMAND)
525  {
526  //Wait for a command from the SCP client
527  error = scpServerReceiveDirective(session, &directive);
528 
529  //Check status code
530  if(!error)
531  {
532  //The source side feeds the commands and the target side consumes them
533  scpServerProcessDirective(session, &directive);
534  }
535  }
536  else if(session->state == SCP_SERVER_SESSION_STATE_WRITE_ACK)
537  {
538  //This status directive indicates a success
539  directive.opcode = SCP_OPCODE_OK;
540  //Send the directive to the SCP client
541  error = scpServerSendDirective(session, &directive);
542 
543  //Check status code
544  if(!error)
545  {
546  //Transfer the contents of the file
547  session->state = SCP_SERVER_SESSION_STATE_WRITE_DATA;
548  }
549  }
550  else if(session->state == SCP_SERVER_SESSION_STATE_WRITE_DATA)
551  {
552  //Write data to the specified file
553  error = scpServerWriteData(session);
554  }
555  else if(session->state == SCP_SERVER_SESSION_STATE_WRITE_STATUS)
556  {
557  //Wait for a status directive from the SCP client
558  error = scpServerReceiveDirective(session, &directive);
559 
560  //Check status code
561  if(!error)
562  {
563  //Check directive opcode
564  if(directive.opcode == SCP_OPCODE_OK)
565  {
566  //A success directive has been received
567  session->state = SCP_SERVER_SESSION_STATE_WRITE_FIN;
568  }
569  else
570  {
571  //A warning or error directive has been received
572  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
573  }
574  }
575  }
576  else if(session->state == SCP_SERVER_SESSION_STATE_WRITE_FIN)
577  {
578  //This status directive indicates a success
579  directive.opcode = SCP_OPCODE_OK;
580  //Send the directive to the SCP client
581  error = scpServerSendDirective(session, &directive);
582 
583  //Check status code
584  if(!error)
585  {
586  //Recursive copy?
587  if(session->recursive || session->targetIsDir)
588  {
589  //Multiple files can be transferred by the client
591  }
592  else
593  {
594  //A single file is transferred by the client
595  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
596  }
597  }
598  }
599  else if(session->state == SCP_SERVER_SESSION_STATE_READ_INIT)
600  {
601  //Wait for a status directive from the SCP client
602  error = scpServerReceiveDirective(session, &directive);
603 
604  //Check status code
605  if(!error)
606  {
607  //Check directive opcode
608  if(directive.opcode == SCP_OPCODE_OK)
609  {
610  //Recursive copy?
611  if(session->recursive)
612  {
613  //Open the specified directory
614  error = scpServerOpenDir(session);
615  }
616  else
617  {
618  //Open the specified file for reading
619  error = scpServerOpenFileForReading(session);
620  }
621 
622  //Check status code
623  if(!error)
624  {
625  //Update SCP session state
626  session->state = SCP_SERVER_SESSION_STATE_READ_COMMAND;
627  }
628  else
629  {
630  //Save status code
631  session->statusCode = error;
632  //Send a status directive to indicate an error
633  session->state = SCP_SERVER_SESSION_STATE_ERROR;
634  //Catch exception
635  error = NO_ERROR;
636  }
637  }
638  else
639  {
640  //A warning or an error message has been received
641  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
642  }
643  }
644  }
645  else if(session->state == SCP_SERVER_SESSION_STATE_READ_COMMAND)
646  {
647  //Format command
648  if(session->file != NULL)
649  {
650  //The 'C' directive indicates the next file to be transferred
651  directive.opcode = SCP_OPCODE_FILE;
652  directive.filename = pathGetFilename(session->path);
653  directive.mode = session->fileMode;
654  directive.size = session->fileSize;
655  }
656  else if(session->dir[session->dirLevel] != NULL)
657  {
658  //The 'D' directive indicates a directory change
659  directive.opcode = SCP_OPCODE_DIR;
660  directive.filename = pathGetFilename(session->path);
661  directive.mode = session->fileMode;
662  directive.size = 0;
663  }
664  else
665  {
666  //The 'E' directive indicates the end of the directory
667  directive.opcode = SCP_OPCODE_END;
668  }
669 
670  //Send the command to the SCP client
671  error = scpServerSendDirective(session, &directive);
672 
673  //Check status code
674  if(!error)
675  {
676  //Update SCP server state
677  session->state = SCP_SERVER_SESSION_STATE_READ_ACK;
678  }
679  }
680  else if(session->state == SCP_SERVER_SESSION_STATE_READ_ACK)
681  {
682  //Wait for a status directive from the SCP client
683  error = scpServerReceiveDirective(session, &directive);
684 
685  //Check status code
686  if(!error)
687  {
688  //Check directive opcode
689  if(directive.opcode == SCP_OPCODE_OK)
690  {
691  if(session->file != NULL)
692  {
693  //Transfer the contents of the file
694  session->state = SCP_SERVER_SESSION_STATE_READ_DATA;
695  }
696  else if(session->dir[session->dirLevel] != NULL)
697  {
698  //Fetch the next entry from the directory
699  scpServerGetNextDirEntry(session);
700  }
701  else
702  {
703  //Change to the parent directory
704  if(session->dirLevel > 0)
705  {
706  session->dirLevel--;
707  }
708 
709  //Valid directory pointer?
710  if(session->dir[session->dirLevel] != NULL)
711  {
712  //Fetch the next entry from the directory
713  scpServerGetNextDirEntry(session);
714  }
715  else
716  {
717  //The copy operation is complete
718  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
719  }
720  }
721  }
722  else
723  {
724  //A warning or an error message has been received
725  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
726  }
727  }
728  }
729  else if(session->state == SCP_SERVER_SESSION_STATE_READ_DATA)
730  {
731  //Read data from the specified file
732  error = scpServerReadData(session);
733  }
734  else if(session->state == SCP_SERVER_SESSION_STATE_READ_STATUS)
735  {
736  //This status directive indicates a success
737  directive.opcode = SCP_OPCODE_OK;
738  //Send the directive to the SCP client
739  error = scpServerSendDirective(session, &directive);
740 
741  //Check status code
742  if(!error)
743  {
744  //Update SCP session state
745  session->state = SCP_SERVER_SESSION_STATE_READ_FIN;
746  }
747  }
748  else if(session->state == SCP_SERVER_SESSION_STATE_READ_FIN)
749  {
750  //Wait for a status directive from the SCP client
751  error = scpServerReceiveDirective(session, &directive);
752 
753  //Check status code
754  if(!error)
755  {
756  //Recursive copy?
757  if(session->recursive)
758  {
759  //Fetch the next entry from the directory
760  scpServerGetNextDirEntry(session);
761  }
762  else
763  {
764  //Update SCP session state
765  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
766  }
767  }
768  }
769  else if(session->state == SCP_SERVER_SESSION_STATE_ERROR)
770  {
771  //This status directive indicates an error
772  directive.opcode = SCP_OPCODE_ERROR;
773 
774  //Warning and error directives can be followed by a textual description
775  if(session->statusCode == ERROR_INVALID_COMMAND)
776  {
777  directive.message = "Invalid command";
778  }
779  else if(session->statusCode == ERROR_INVALID_PATH)
780  {
781  directive.message = "Invalid path";
782  }
783  else if(session->statusCode == ERROR_FILE_NOT_FOUND)
784  {
785  directive.message = "No such file";
786  }
787  else if(session->statusCode == ERROR_DIRECTORY_NOT_FOUND)
788  {
789  directive.message = "No such directory";
790  }
791  else if(session->statusCode == ERROR_ACCESS_DENIED)
792  {
793  directive.message = "Access denied";
794  }
795  else
796  {
797  directive.message = "Protocol error";
798  }
799 
800  //Send the directive to the SCP client
801  error = scpServerSendDirective(session, &directive);
802 
803  //Check status code
804  if(!error)
805  {
806  //Update SCP session state
807  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
808  }
809  }
810  else if(session->state == SCP_SERVER_SESSION_STATE_CLOSING)
811  {
812  //Close SCP session
813  scpServerCloseSession(session);
814  }
815  else
816  {
817  //Invalid state
818  error = ERROR_WRONG_STATE;
819  }
820 
821  //Any communication error?
822  if(error != NO_ERROR && error != ERROR_WOULD_BLOCK && error != ERROR_TIMEOUT)
823  {
824  //Close the SSH connection
825  scpServerCloseSession(session);
826  }
827 }
828 
829 
830 /**
831  * @brief Send a SCP directive to the client
832  * @param[in] session Handle referencing an SCP session
833  * @param[in] directive SCP directive parameters
834  * @return Error code
835  **/
836 
838  const ScpDirective *directive)
839 {
840  error_t error;
841  size_t n;
842 
843  //Initialize status code
844  error = NO_ERROR;
845 
846  //Format and and send status message
847  while(!error)
848  {
849  //Manage message transmission
850  if(session->bufferLen == 0)
851  {
852  //Format directive line
853  n = scpFormatDirective(directive, session->buffer);
854 
855  //Save the length of the directive line
856  session->bufferLen = n;
857  session->bufferPos = 0;
858  }
859  else if(session->bufferPos < session->bufferLen)
860  {
861  //Send more data
862  error = sshWriteChannel(session->channel,
863  session->buffer + session->bufferPos,
864  session->bufferLen - session->bufferPos, &n, 0);
865 
866  //Check status code
867  if(error == NO_ERROR || error == ERROR_TIMEOUT)
868  {
869  //Advance data pointer
870  session->bufferPos += n;
871  }
872  }
873  else
874  {
875  //Flush transmit buffer
876  session->bufferLen = 0;
877  session->bufferPos = 0;
878 
879  //We are done
880  break;
881  }
882  }
883 
884  //Return status code
885  return error;
886 }
887 
888 
889 /**
890  * @brief Receive a SCP directive from the client
891  * @param[in] session Handle referencing an SCP session
892  * @param[in] directive SCP directive parameters
893  * @return Error code
894  **/
895 
897  ScpDirective *directive)
898 {
899  error_t error;
900  size_t n;
901  uint8_t opcode;
902 
903  //Initialize status code
904  error = NO_ERROR;
905 
906  //Receive and parse SCP directive
907  while(!error)
908  {
909  //Manage message reception
910  if(session->bufferLen == 0)
911  {
912  //Read the directive opcode
913  error = sshReadChannel(session->channel, session->buffer, 1, &n, 0);
914 
915  //Check status code
916  if(!error)
917  {
918  //Adjust the length of the buffer
919  session->bufferLen += n;
920  }
921  }
922  else if(session->bufferLen < SCP_SERVER_BUFFER_SIZE)
923  {
924  //Retrieve directive opcode
925  opcode = session->buffer[0];
926 
927  //Check directive opcode
928  if(opcode == SCP_OPCODE_OK)
929  {
930  //Parse the received directive
931  error = scpParseDirective(session->buffer, directive);
932 
933  //Flush receive buffer
934  session->bufferLen = 0;
935  session->bufferPos = 0;
936 
937  //We are done
938  break;
939  }
940  else if(opcode == SCP_OPCODE_WARNING ||
942  opcode == SCP_OPCODE_FILE ||
943  opcode == SCP_OPCODE_DIR ||
944  opcode == SCP_OPCODE_END ||
946  {
947  //Limit the number of bytes to read at a time
948  n = SCP_SERVER_BUFFER_SIZE - session->bufferLen;
949 
950  //Read more data
951  error = sshReadChannel(session->channel, session->buffer +
952  session->bufferLen, n, &n, SSH_FLAG_BREAK_CRLF);
953 
954  //Check status code
955  if(!error)
956  {
957  //Adjust the length of the buffer
958  session->bufferLen += n;
959 
960  //Check whether the string is properly terminated
961  if(session->bufferLen > 0 &&
962  session->buffer[session->bufferLen - 1] == '\n')
963  {
964  //Properly terminate the string with a NULL character
965  session->buffer[session->bufferLen - 1] = '\0';
966 
967  //Parse the received directive
968  error = scpParseDirective(session->buffer, directive);
969 
970  //Flush receive buffer
971  session->bufferLen = 0;
972  session->bufferPos = 0;
973 
974  //We are done
975  break;
976  }
977  else
978  {
979  //Wait for a new line character
980  error = ERROR_WOULD_BLOCK;
981  }
982  }
983  }
984  else
985  {
986  //Unknown directive
987  error = ERROR_INVALID_COMMAND;
988  }
989  }
990  else
991  {
992  //The implementation limits the size of messages it accepts
993  error = ERROR_BUFFER_OVERFLOW;
994  }
995  }
996 
997  //Return status code
998  return error;
999 }
1000 
1001 
1002 /**
1003  * @brief Process SCP directive
1004  * @param[in] session Handle referencing an SCP session
1005  * @param[in] directive SCP directive sent by the client
1006  **/
1007 
1009  const ScpDirective *directive)
1010 {
1011  error_t error;
1012 
1013  //Check directive opcode
1014  if(directive->opcode == SCP_OPCODE_FILE)
1015  {
1016  //The file name must not contain illegal characters
1017  if(osStrcmp(directive->filename, ".") == 0 ||
1018  osStrcmp(directive->filename, "..") == 0 ||
1019  osStrchr(directive->filename, '*') != NULL ||
1020  osStrchr(directive->filename, '/') != NULL ||
1021  osStrchr(directive->filename, '\\') != NULL)
1022  {
1023  //Save status code
1024  session->statusCode = ERROR_INVALID_PATH;
1025  //Send a status directive to indicate an error
1026  session->state = SCP_SERVER_SESSION_STATE_ERROR;
1027  }
1028  else
1029  {
1030  //Open the specified file for reading
1031  error = scpServerOpenFileForWriting(session, directive->filename,
1032  directive->mode, directive->size);
1033 
1034  //Check status code
1035  if(!error)
1036  {
1037  //Initiate data transfer
1038  session->state = SCP_SERVER_SESSION_STATE_WRITE_ACK;
1039  }
1040  else
1041  {
1042  //Save status code
1043  session->statusCode = ERROR_FILE_NOT_FOUND;
1044  //Send a status directive to indicate an error
1045  session->state = SCP_SERVER_SESSION_STATE_ERROR;
1046  }
1047  }
1048  }
1049  else if(directive->opcode == SCP_OPCODE_DIR)
1050  {
1051  //The file name must not contain illegal characters
1052  if(osStrcmp(directive->filename, ".") == 0 ||
1053  osStrcmp(directive->filename, "..") == 0 ||
1054  osStrchr(directive->filename, '*') != NULL ||
1055  osStrchr(directive->filename, '/') != NULL ||
1056  osStrchr(directive->filename, '\\') != NULL)
1057  {
1058  //Save status code
1059  session->statusCode = ERROR_INVALID_PATH;
1060  //Send a status directive to indicate an error
1061  session->state = SCP_SERVER_SESSION_STATE_ERROR;
1062  }
1063  else
1064  {
1065  //If the folder does not exist, then create it
1066  error = scpServerCreateDir(session, directive->filename);
1067 
1068  //Check status code
1069  if(!error)
1070  {
1071  //Wait for the next command
1072  session->state = SCP_SERVER_SESSION_STATE_WRITE_INIT;
1073  }
1074  else
1075  {
1076  //Save status code
1077  session->statusCode = error;
1078  //Send a status directive to indicate an error
1079  session->state = SCP_SERVER_SESSION_STATE_ERROR;
1080  }
1081  }
1082  }
1083  else if(directive->opcode == SCP_OPCODE_END)
1084  {
1085  //Check current level of recursion
1086  if(session->dirLevel > 0)
1087  {
1088  //Change to the parent directory
1089  pathRemoveFilename(session->path);
1090  pathRemoveSlash(session->path);
1091 
1092  //Decrement recursion level
1093  session->dirLevel--;
1094  //Wait for the next command
1095  session->state = SCP_SERVER_SESSION_STATE_WRITE_INIT;
1096  }
1097  else
1098  {
1099  //Report an error
1100  session->statusCode = ERROR_DIRECTORY_NOT_FOUND;
1101  //Send a status directive to indicate an error
1102  session->state = SCP_SERVER_SESSION_STATE_ERROR;
1103  }
1104  }
1105  else if(directive->opcode == SCP_OPCODE_TIME)
1106  {
1107  //Discard time directives
1108  session->state = SCP_SERVER_SESSION_STATE_WRITE_INIT;
1109  }
1110  else
1111  {
1112  //A warning or an error message has been received
1113  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
1114  }
1115 }
1116 
1117 
1118 /**
1119  * @brief Get permissions for the specified file or directory
1120  * @param[in] session Handle referencing an SCP session
1121  * @param[in] path Canonical path of the file
1122  * @return Access rights for the specified file
1123  **/
1124 
1126  const char_t *path)
1127 {
1128  size_t n;
1129  uint_t perm;
1130  ScpServerContext *context;
1131 
1132  //Point to the SCP server context
1133  context = session->context;
1134 
1135  //Calculate the length of the root directory
1136  n = osStrlen(session->rootDir);
1137 
1138  //Make sure the pathname is valid
1139  if(osStrncmp(path, session->rootDir, n) == 0)
1140  {
1141  //Strip root directory from the pathname
1142  path = scpServerStripRootDir(session, path);
1143 
1144  //Invoke user-defined callback, if any
1145  if(context->getFilePermCallback != NULL)
1146  {
1147  //Retrieve access rights for the specified file
1148  perm = context->getFilePermCallback(session,
1149  session->channel->connection->user, path);
1150  }
1151  else
1152  {
1153  //Use default access rights
1156  }
1157  }
1158  else
1159  {
1160  //The specified pathname is not valid
1161  perm = 0;
1162  }
1163 
1164  //Return access rights
1165  return perm;
1166 }
1167 
1168 
1169 /**
1170  * @brief Retrieve the full pathname
1171  * @param[in] session Handle referencing an SCP session
1172  * @param[in] path Relative or absolute path
1173  * @param[out] fullPath Resulting full path
1174  * @param[in] maxLen Maximum acceptable path length
1175  * @return Error code
1176  **/
1177 
1179  char_t *fullPath, size_t maxLen)
1180 {
1181  size_t n;
1182 
1183  //Relative or absolute path?
1184  if(path->length > 0 && (path->value[0] == '/' || path->value[0] == '\\'))
1185  {
1186  //Check the length of the root directory
1187  if(osStrlen(session->rootDir) > maxLen)
1188  return ERROR_FAILURE;
1189 
1190  //Copy the root directory
1191  osStrcpy(fullPath, session->rootDir);
1192  }
1193  else
1194  {
1195  //Check the length of the home directory
1196  if(osStrlen(session->homeDir) > maxLen)
1197  return ERROR_FAILURE;
1198 
1199  //Copy the home directory
1200  osStrcpy(fullPath, session->homeDir);
1201  }
1202 
1203  //Append a slash character to the root directory
1204  if(fullPath[0] != '\0')
1205  {
1206  pathAddSlash(fullPath, maxLen);
1207  }
1208 
1209  //Retrieve the length of the path name
1210  n = osStrlen(fullPath);
1211 
1212  //Check the length of the full path name
1213  if((n + path->length) > maxLen)
1214  return ERROR_FAILURE;
1215 
1216  //Append the specified path
1217  osStrncpy(fullPath + n, path->value, path->length);
1218  //Properly terminate the string with a NULL character
1219  fullPath[n + path->length] = '\0';
1220 
1221  //Clean the resulting path
1222  pathCanonicalize(fullPath);
1223  pathRemoveSlash(fullPath);
1224 
1225  //Calculate the length of the home directory
1226  n = osStrlen(session->rootDir);
1227 
1228  //If the server implementation limits access to certain parts of the file
1229  //system, it must be extra careful in parsing file names when enforcing
1230  //such restrictions
1231  if(osStrncmp(fullPath, session->rootDir, n) != 0)
1232  return ERROR_INVALID_PATH;
1233 
1234  //Successful processing
1235  return NO_ERROR;
1236 }
1237 
1238 
1239 /**
1240  * @brief Strip root dir from specified pathname
1241  * @param[in] session Handle referencing an SCP session
1242  * @param[in] path input pathname
1243  * @return Resulting pathname with root dir stripped
1244  **/
1245 
1247  const char_t *path)
1248 {
1249  //Default directory
1250  static const char_t defaultDir[] = "/";
1251 
1252  //Local variables
1253  size_t m;
1254  size_t n;
1255 
1256  //Retrieve the length of the root directory
1257  n = osStrlen(session->rootDir);
1258  //Retrieve the length of the specified pathname
1259  m = osStrlen(path);
1260 
1261  //Strip the root dir from the specified pathname
1262  if(n <= 1)
1263  {
1264  return path;
1265  }
1266  else if(n < m)
1267  {
1268  return path + n;
1269  }
1270  else
1271  {
1272  return defaultDir;
1273  }
1274 }
1275 
1276 #endif
Path manipulation helper functions.
#define osStrchr(s, c)
Definition: os_port.h:195
uint8_t opcode
Definition: dns_common.h:188
int bool_t
Definition: compiler_port.h:53
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:1553
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:155
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:142
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
#define TRUE
Definition: os_port.h:50
uint8_t data[]
Definition: ethernet.h:222
error_t sshCloseChannel(SshChannel *channel)
Close channel.
Definition: ssh.c:2465
@ 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:1554
@ SCP_SERVER_SESSION_STATE_WRITE_COMMAND
Definition: scp_server.h:152
@ 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:1552
size_t length
Definition: ssh_types.h:58
#define osStrcmp(s1, s2)
Definition: os_port.h:171
error_t scpServerOpenFileForReading(ScpServerSession *session)
Open a file for reading.
#define ScpServerSession
Definition: scp_server.h:113
#define osStrlen(s)
Definition: os_port.h:165
@ SCP_SERVER_SESSION_STATE_WRITE_INIT
Definition: scp_server.h:151
@ 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:139
bool_t sshCompareString(const SshString *string, const char_t *value)
Compare a binary string against the supplied value.
Definition: ssh_misc.c:1586
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:209
ScpAccessStatus
Access status.
Definition: scp_server.h:126
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
Directory operations.
@ SSH_CHANNEL_EVENT_TX_READY
Definition: ssh.h:1102
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:158
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:164
@ SCP_SERVER_SESSION_STATE_WRITE_ACK
Definition: scp_server.h:153
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:161
error_t scpServerReceiveDirective(ScpServerSession *session, ScpDirective *directive)
Receive a SCP directive from the client.
@ ERROR_FILE_NOT_FOUND
Definition: error.h:156
@ SCP_SERVER_SESSION_STATE_ERROR
Definition: scp_server.h:163
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:277
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:2051
@ SCP_OPCODE_DIR
Definition: scp_common.h:67
#define ScpServerContext
Definition: scp_server.h:109
@ ERROR_ACCESS_DENIED
Definition: error.h:148
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:95
uint8_t length
Definition: tcp.h:368
void pathAddSlash(char_t *path, size_t maxLen)
Add a slash to the end of a string.
Definition: path.c:332
SCP server.
void scpServerRegisterSessionEvents(ScpServerSession *session, SshChannelEventDesc *eventDesc)
Register session events.
@ SCP_FILE_PERM_WRITE
Definition: scp_server.h:140
String.
Definition: ssh_types.h:56
File operations.
const char_t * scpServerStripRootDir(ScpServerSession *session, const char_t *path)
Strip root dir from specified pathname.
@ ERROR_INVALID_PATH
Definition: error.h:146
@ SCP_SERVER_SESSION_STATE_WRITE_FIN
Definition: scp_server.h:156
@ ERROR_DIRECTORY_NOT_FOUND
Definition: error.h:164
@ SCP_SERVER_SESSION_STATE_READ_COMMAND
Definition: scp_server.h:158
@ ERROR_TIMEOUT
Definition: error.h:95
char char_t
Definition: compiler_port.h:48
@ SCP_SERVER_SESSION_STATE_CLOSED
Definition: scp_server.h:150
#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:1551
uint8_t m
Definition: ndp.h:304
uint8_t n
@ SSH_CHANNEL_EVENT_RX_READY
Definition: ssh.h:1106
@ SCP_FILE_PERM_LIST
Definition: scp_server.h:138
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:157
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:918
#define osStrncpy(s1, s2, length)
Definition: os_port.h:213
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:177
void pathRemoveSlash(char_t *path)
Remove the trailing slash from a given path.
Definition: path.c:360
@ SCP_SERVER_SESSION_STATE_WRITE_DATA
Definition: scp_server.h:154
void fsCloseDir(FsDir *dir)
Close a directory stream.
unsigned int uint_t
Definition: compiler_port.h:50
#define osMemset(p, value, length)
Definition: os_port.h:135
@ 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:2027
Secure Shell (SSH)
@ SCP_OPCODE_WARNING
Definition: scp_common.h:64
@ SCP_SERVER_SESSION_STATE_READ_ACK
Definition: scp_server.h:159
#define osStrcpy(s1, s2)
Definition: os_port.h:207
void pathRemoveFilename(char_t *path)
Remove the trailing file name from the supplied path.
Definition: path.c:120
Global request and channel request handling.
@ SCP_SERVER_SESSION_STATE_READ_FIN
Definition: scp_server.h:162
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.
@ SCP_SERVER_SESSION_STATE_READ_DATA
Definition: scp_server.h:160
#define SshChannel
Definition: ssh.h:878
void pathCopy(char_t *dest, const char_t *src, size_t maxLen)
Copy a path.
Definition: path.c:137
@ SCP_ACCESS_ALLOWED
Definition: scp_server.h:128
bool_t sshGetExecArg(const SshExecParams *params, uint_t index, SshString *arg)
Retrieve the specified argument from an "exec" request.
Definition: ssh_request.c:1540