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.0
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,
914  &n, 0);
915 
916  //Check status code
917  if(!error)
918  {
919  //Adjust the length of the buffer
920  session->bufferLen += n;
921  }
922  }
923  else if(session->bufferLen < SCP_SERVER_BUFFER_SIZE)
924  {
925  //Retrieve directive opcode
926  opcode = session->buffer[0];
927 
928  //Check directive opcode
929  if(opcode == SCP_OPCODE_OK)
930  {
931  //Parse the received directive
932  error = scpParseDirective(session->buffer, directive);
933 
934  //Flush receive buffer
935  session->bufferLen = 0;
936  session->bufferPos = 0;
937 
938  //We are done
939  break;
940  }
941  else if(opcode == SCP_OPCODE_WARNING ||
943  opcode == SCP_OPCODE_FILE ||
944  opcode == SCP_OPCODE_DIR ||
945  opcode == SCP_OPCODE_END ||
947  {
948  //Limit the number of bytes to read at a time
949  n = SCP_SERVER_BUFFER_SIZE - session->bufferLen;
950 
951  //Read more data
952  error = sshReadChannel(session->channel, session->buffer +
953  session->bufferLen, n, &n, SSH_FLAG_BREAK_CRLF);
954 
955  //Check status code
956  if(!error)
957  {
958  //Adjust the length of the buffer
959  session->bufferLen += n;
960 
961  //Check whether the string is properly terminated
962  if(session->bufferLen > 0 &&
963  session->buffer[session->bufferLen - 1] == '\n')
964  {
965  //Properly terminate the string with a NULL character
966  session->buffer[session->bufferLen - 1] = '\0';
967 
968  //Parse the received directive
969  error = scpParseDirective(session->buffer, directive);
970 
971  //Flush receive buffer
972  session->bufferLen = 0;
973  session->bufferPos = 0;
974 
975  //We are done
976  break;
977  }
978  else
979  {
980  //Wait for a new line character
981  error = ERROR_WOULD_BLOCK;
982  }
983  }
984  }
985  else
986  {
987  //Unknown directive
988  error = ERROR_INVALID_COMMAND;
989  }
990  }
991  else
992  {
993  //The implementation limits the size of messages it accepts
994  error = ERROR_BUFFER_OVERFLOW;
995  }
996  }
997 
998  //Return status code
999  return error;
1000 }
1001 
1002 
1003 /**
1004  * @brief Process SCP directive
1005  * @param[in] session Handle referencing an SCP session
1006  * @param[in] directive SCP directive sent by the client
1007  **/
1008 
1010  const ScpDirective *directive)
1011 {
1012  error_t error;
1013 
1014  //Check directive opcode
1015  if(directive->opcode == SCP_OPCODE_FILE)
1016  {
1017  //The file name must not contain illegal characters
1018  if(!osStrcmp(directive->filename, ".") ||
1019  !osStrcmp(directive->filename, "..") ||
1020  osStrchr(directive->filename, '*') ||
1021  osStrchr(directive->filename, '/') ||
1022  osStrchr(directive->filename, '\\'))
1023  {
1024  //Save status code
1025  session->statusCode = ERROR_INVALID_PATH;
1026  //Send a status directive to indicate an error
1027  session->state = SCP_SERVER_SESSION_STATE_ERROR;
1028  }
1029  else
1030  {
1031  //Open the specified file for reading
1032  error = scpServerOpenFileForWriting(session, directive->filename,
1033  directive->mode, directive->size);
1034 
1035  //Check status code
1036  if(!error)
1037  {
1038  //Initiate data transfer
1039  session->state = SCP_SERVER_SESSION_STATE_WRITE_ACK;
1040  }
1041  else
1042  {
1043  //Save status code
1044  session->statusCode = ERROR_FILE_NOT_FOUND;
1045  //Send a status directive to indicate an error
1046  session->state = SCP_SERVER_SESSION_STATE_ERROR;
1047  }
1048  }
1049  }
1050  else if(directive->opcode == SCP_OPCODE_DIR)
1051  {
1052  //The file name must not contain illegal characters
1053  if(!osStrcmp(directive->filename, ".") ||
1054  !osStrcmp(directive->filename, "..") ||
1055  osStrchr(directive->filename, '*') ||
1056  osStrchr(directive->filename, '/') ||
1057  osStrchr(directive->filename, '\\'))
1058  {
1059  //Save status code
1060  session->statusCode = ERROR_INVALID_PATH;
1061  //Send a status directive to indicate an error
1062  session->state = SCP_SERVER_SESSION_STATE_ERROR;
1063  }
1064  else
1065  {
1066  //If the folder does not exist, then create it
1067  error = scpServerCreateDir(session, directive->filename);
1068 
1069  //Check status code
1070  if(!error)
1071  {
1072  //Wait for the next command
1073  session->state = SCP_SERVER_SESSION_STATE_WRITE_INIT;
1074  }
1075  else
1076  {
1077  //Save status code
1078  session->statusCode = error;
1079  //Send a status directive to indicate an error
1080  session->state = SCP_SERVER_SESSION_STATE_ERROR;
1081  }
1082  }
1083  }
1084  else if(directive->opcode == SCP_OPCODE_END)
1085  {
1086  //Check current level of recursion
1087  if(session->dirLevel > 0)
1088  {
1089  //Change to the parent directory
1090  pathRemoveFilename(session->path);
1091  pathRemoveSlash(session->path);
1092 
1093  //Decrement recursion level
1094  session->dirLevel--;
1095  //Wait for the next command
1096  session->state = SCP_SERVER_SESSION_STATE_WRITE_INIT;
1097  }
1098  else
1099  {
1100  //Report an error
1101  session->statusCode = ERROR_DIRECTORY_NOT_FOUND;
1102  //Send a status directive to indicate an error
1103  session->state = SCP_SERVER_SESSION_STATE_ERROR;
1104  }
1105  }
1106  else if(directive->opcode == SCP_OPCODE_TIME)
1107  {
1108  //Discard time directives
1109  session->state = SCP_SERVER_SESSION_STATE_WRITE_INIT;
1110  }
1111  else
1112  {
1113  //A warning or an error message has been received
1114  session->state = SCP_SERVER_SESSION_STATE_CLOSING;
1115  }
1116 }
1117 
1118 
1119 /**
1120  * @brief Get permissions for the specified file or directory
1121  * @param[in] session Handle referencing an SCP session
1122  * @param[in] path Canonical path of the file
1123  * @return Access rights for the specified file
1124  **/
1125 
1127  const char_t *path)
1128 {
1129  size_t n;
1130  uint_t perm;
1131  ScpServerContext *context;
1132 
1133  //Point to the SCP server context
1134  context = session->context;
1135 
1136  //Calculate the length of the root directory
1137  n = osStrlen(session->rootDir);
1138 
1139  //Make sure the pathname is valid
1140  if(!osStrncmp(path, session->rootDir, n))
1141  {
1142  //Strip root directory from the pathname
1143  path = scpServerStripRootDir(session, path);
1144 
1145  //Invoke user-defined callback, if any
1146  if(context->getFilePermCallback != NULL)
1147  {
1148  //Retrieve access rights for the specified file
1149  perm = context->getFilePermCallback(session,
1150  session->channel->connection->user, path);
1151  }
1152  else
1153  {
1154  //Use default access rights
1157  }
1158  }
1159  else
1160  {
1161  //The specified pathname is not valid
1162  perm = 0;
1163  }
1164 
1165  //Return access rights
1166  return perm;
1167 }
1168 
1169 
1170 /**
1171  * @brief Retrieve the full pathname
1172  * @param[in] session Handle referencing an SCP session
1173  * @param[in] path Relative or absolute path
1174  * @param[out] fullPath Resulting full path
1175  * @param[in] maxLen Maximum acceptable path length
1176  * @return Error code
1177  **/
1178 
1180  char_t *fullPath, size_t maxLen)
1181 {
1182  size_t n;
1183 
1184  //Relative or absolute path?
1185  if(path->length > 0 && (path->value[0] == '/' || path->value[0] == '\\'))
1186  {
1187  //Check the length of the root directory
1188  if(osStrlen(session->rootDir) > maxLen)
1189  return ERROR_FAILURE;
1190 
1191  //Copy the root directory
1192  osStrcpy(fullPath, session->rootDir);
1193  }
1194  else
1195  {
1196  //Check the length of the home directory
1197  if(osStrlen(session->homeDir) > maxLen)
1198  return ERROR_FAILURE;
1199 
1200  //Copy the home directory
1201  osStrcpy(fullPath, session->homeDir);
1202  }
1203 
1204  //Append a slash character to the root directory
1205  if(fullPath[0] != '\0')
1206  {
1207  pathAddSlash(fullPath, maxLen);
1208  }
1209 
1210  //Retrieve the length of the path name
1211  n = osStrlen(fullPath);
1212 
1213  //Check the length of the full path name
1214  if((n + path->length) > maxLen)
1215  return ERROR_FAILURE;
1216 
1217  //Append the specified path
1218  osStrncpy(fullPath + n, path->value, path->length);
1219  //Properly terminate the string with a NULL character
1220  fullPath[n + path->length] = '\0';
1221 
1222  //Clean the resulting path
1223  pathCanonicalize(fullPath);
1224  pathRemoveSlash(fullPath);
1225 
1226  //Calculate the length of the home directory
1227  n = osStrlen(session->rootDir);
1228 
1229  //If the server implementation limits access to certain parts of the file
1230  //system, it must be extra careful in parsing file names when enforcing
1231  //such restrictions
1232  if(osStrncmp(fullPath, session->rootDir, n))
1233  return ERROR_INVALID_PATH;
1234 
1235  //Successful processing
1236  return NO_ERROR;
1237 }
1238 
1239 
1240 /**
1241  * @brief Strip root dir from specified pathname
1242  * @param[in] session Handle referencing an SCP session
1243  * @param[in] path input pathname
1244  * @return Resulting pathname with root dir stripped
1245  **/
1246 
1248  const char_t *path)
1249 {
1250  //Default directory
1251  static const char_t defaultDir[] = "/";
1252 
1253  //Local variables
1254  size_t m;
1255  size_t n;
1256 
1257  //Retrieve the length of the root directory
1258  n = osStrlen(session->rootDir);
1259  //Retrieve the length of the specified pathname
1260  m = osStrlen(path);
1261 
1262  //Strip the root dir from the specified pathname
1263  if(n <= 1)
1264  {
1265  return path;
1266  }
1267  else if(n < m)
1268  {
1269  return path + n;
1270  }
1271  else
1272  {
1273  return defaultDir;
1274  }
1275 }
1276 
1277 #endif
uint8_t type
Definition: coap_common.h:176
unsigned int uint_t
Definition: compiler_port.h:50
char char_t
Definition: compiler_port.h:48
int bool_t
Definition: compiler_port.h:53
Debugging facilities.
#define TRACE_INFO(...)
Definition: debug.h:95
uint8_t n
uint8_t opcode
Definition: dns_common.h:188
error_t
Error codes.
Definition: error.h:43
@ ERROR_WOULD_BLOCK
Definition: error.h:96
@ ERROR_FILE_NOT_FOUND
Definition: error.h:156
@ ERROR_TIMEOUT
Definition: error.h:95
@ ERROR_OUT_OF_RESOURCES
Definition: error.h:64
@ ERROR_INVALID_PATH
Definition: error.h:146
@ ERROR_ACCESS_DENIED
Definition: error.h:148
@ ERROR_INVALID_COMMAND
Definition: error.h:100
@ ERROR_UNKNOWN_REQUEST
Definition: error.h:276
@ NO_ERROR
Success.
Definition: error.h:44
@ ERROR_BUFFER_OVERFLOW
Definition: error.h:142
@ ERROR_WRONG_STATE
Definition: error.h:209
@ ERROR_FAILURE
Generic error code.
Definition: error.h:45
@ ERROR_DIRECTORY_NOT_FOUND
Definition: error.h:164
uint8_t data[]
Definition: ethernet.h:222
void fsCloseFile(FsFile *file)
Close a file.
void fsCloseDir(FsDir *dir)
Close a directory stream.
uint8_t t
Definition: lldp_ext_med.h:212
uint8_t r
Definition: ndp.h:346
uint8_t m
Definition: ndp.h:304
#define osMemset(p, value, length)
Definition: os_port.h:135
#define osStrcmp(s1, s2)
Definition: os_port.h:171
#define osStrchr(s, c)
Definition: os_port.h:195
#define osStrlen(s)
Definition: os_port.h:165
#define osStrncmp(s1, s2, length)
Definition: os_port.h:177
#define TRUE
Definition: os_port.h:50
#define FALSE
Definition: os_port.h:46
#define osStrcpy(s1, s2)
Definition: os_port.h:207
#define osStrncpy(s1, s2, length)
Definition: os_port.h:213
void osSetEvent(OsEvent *event)
Set the specified event object to the signaled state.
void pathCanonicalize(char_t *path)
Simplify a path.
Definition: path.c:150
const char_t * pathGetFilename(const char_t *path)
Extract the file name from the supplied path.
Definition: path.c:73
void pathCopy(char_t *dest, const char_t *src, size_t maxLen)
Copy a path.
Definition: path.c:129
void pathAddSlash(char_t *path, size_t maxLen)
Add a slash to the end of a string.
Definition: path.c:312
void pathRemoveFilename(char_t *path)
Remove the trailing file name from the supplied path.
Definition: path.c:112
void pathRemoveSlash(char_t *path)
Remove the trailing slash from a given path.
Definition: path.c:340
Path manipulation helper functions.
size_t scpFormatDirective(const ScpDirective *directive, char_t *buffer)
Format SCP directive.
Definition: scp_common.c:48
error_t scpParseDirective(const char_t *buffer, ScpDirective *directive)
Parse SCP directive.
Definition: scp_common.c:127
@ SCP_OPCODE_END
Definition: scp_common.h:68
@ SCP_OPCODE_WARNING
Definition: scp_common.h:64
@ SCP_OPCODE_OK
Definition: scp_common.h:63
@ SCP_OPCODE_TIME
Definition: scp_common.h:69
@ SCP_OPCODE_ERROR
Definition: scp_common.h:65
@ SCP_OPCODE_DIR
Definition: scp_common.h:67
@ SCP_OPCODE_FILE
Definition: scp_common.h:66
SCP server.
@ SCP_SERVER_SESSION_STATE_ERROR
Definition: scp_server.h:163
@ SCP_SERVER_SESSION_STATE_CLOSING
Definition: scp_server.h:164
@ SCP_SERVER_SESSION_STATE_WRITE_FIN
Definition: scp_server.h:156
@ SCP_SERVER_SESSION_STATE_WRITE_COMMAND
Definition: scp_server.h:152
@ SCP_SERVER_SESSION_STATE_CLOSED
Definition: scp_server.h:150
@ SCP_SERVER_SESSION_STATE_WRITE_ACK
Definition: scp_server.h:153
@ SCP_SERVER_SESSION_STATE_READ_STATUS
Definition: scp_server.h:161
@ SCP_SERVER_SESSION_STATE_WRITE_STATUS
Definition: scp_server.h:155
@ SCP_SERVER_SESSION_STATE_WRITE_INIT
Definition: scp_server.h:151
@ SCP_SERVER_SESSION_STATE_READ_FIN
Definition: scp_server.h:162
@ SCP_SERVER_SESSION_STATE_READ_DATA
Definition: scp_server.h:160
@ SCP_SERVER_SESSION_STATE_WRITE_DATA
Definition: scp_server.h:154
@ SCP_SERVER_SESSION_STATE_READ_ACK
Definition: scp_server.h:159
@ SCP_SERVER_SESSION_STATE_READ_COMMAND
Definition: scp_server.h:158
@ SCP_SERVER_SESSION_STATE_READ_INIT
Definition: scp_server.h:157
ScpAccessStatus
Access status.
Definition: scp_server.h:126
@ SCP_ACCESS_ALLOWED
Definition: scp_server.h:128
#define SCP_SERVER_MAX_ROOT_DIR_LEN
Definition: scp_server.h:81
#define ScpServerContext
Definition: scp_server.h:109
#define SCP_SERVER_BUFFER_SIZE
Definition: scp_server.h:74
@ SCP_FILE_PERM_READ
Definition: scp_server.h:139
@ SCP_FILE_PERM_LIST
Definition: scp_server.h:138
@ SCP_FILE_PERM_WRITE
Definition: scp_server.h:140
#define SCP_SERVER_MAX_RECURSION_LEVEL
Definition: scp_server.h:102
#define ScpServerSession
Definition: scp_server.h:113
#define SCP_SERVER_MAX_PATH_LEN
Definition: scp_server.h:95
#define SCP_SERVER_MAX_HOME_DIR_LEN
Definition: scp_server.h:88
error_t scpServerOpenDir(ScpServerSession *session)
Open a directory.
void scpServerGetNextDirEntry(ScpServerSession *session)
Fetch the next entry from the directory.
error_t scpServerCreateDir(ScpServerSession *session, const char_t *name)
Create a directory.
Directory operations.
error_t scpServerReadData(ScpServerSession *session)
Read data from the specified file.
error_t scpServerWriteData(ScpServerSession *session)
Write data to the specified file.
error_t scpServerOpenFileForReading(ScpServerSession *session)
Open a file for reading.
error_t scpServerOpenFileForWriting(ScpServerSession *session, const char_t *filename, uint32_t mode, uint64_t size)
Open a file for writing.
File operations.
ScpServerSession * scpServerOpenSession(ScpServerContext *context, SshChannel *channel)
Open a new SCP session.
uint_t scpServerGetFilePermissions(ScpServerSession *session, const char_t *path)
Get permissions for the specified file or directory.
error_t scpServerGetPath(ScpServerSession *session, const SshString *path, char_t *fullPath, size_t maxLen)
Retrieve the full pathname.
ScpServerSession * scpServerFindSession(ScpServerContext *context, SshChannel *channel)
Find the SCP session that matches a given SSH channel.
void scpServerTick(ScpServerContext *context)
Handle periodic operations.
void scpServerParseCommandLine(ScpServerSession *session, const SshExecParams *requestParams)
SCP command line parsing.
void scpServerProcessDirective(ScpServerSession *session, const ScpDirective *directive)
Process SCP directive.
error_t scpServerChannelRequestCallback(SshChannel *channel, const SshString *type, const uint8_t *data, size_t length, void *param)
SSH channel request callback.
error_t scpServerReceiveDirective(ScpServerSession *session, ScpDirective *directive)
Receive a SCP directive from the client.
void scpServerProcessSessionEvents(ScpServerSession *session)
Session event handler.
error_t scpServerSendDirective(ScpServerSession *session, const ScpDirective *directive)
Send a SCP directive to the client.
void scpServerRegisterSessionEvents(ScpServerSession *session, SshChannelEventDesc *eventDesc)
Register session events.
const char_t * scpServerStripRootDir(ScpServerSession *session, const char_t *path)
Strip root dir from specified pathname.
void scpServerCloseSession(ScpServerSession *session)
Close an SCP session.
Helper functions for SCP server.
error_t sshSetChannelTimeout(SshChannel *channel, systime_t timeout)
Set timeout for read/write operations.
Definition: ssh.c:2027
error_t sshCloseChannel(SshChannel *channel)
Close channel.
Definition: ssh.c:2465
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
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
Secure Shell (SSH)
#define SshChannel
Definition: ssh.h:887
@ SSH_FLAG_BREAK_CRLF
Definition: ssh.h:927
@ SSH_CHANNEL_EVENT_TX_READY
Definition: ssh.h:1111
@ SSH_CHANNEL_EVENT_RX_READY
Definition: ssh.h:1115
bool_t sshCompareString(const SshString *string, const char_t *value)
Compare a binary string against the supplied value.
Definition: ssh_misc.c:1586
SSH helper functions.
bool_t sshGetExecArg(const SshExecParams *params, uint_t index, SshString *arg)
Retrieve the specified argument from an "exec" request.
Definition: ssh_request.c:1540
error_t sshParseExecParams(const uint8_t *p, size_t length, SshExecParams *params)
Parse "exec" channel request parameters.
Definition: ssh_request.c:1512
Global request and channel request handling.
SCP directive parameters.
Definition: scp_common.h:78
const char_t * filename
Definition: scp_common.h:84
uint32_t mode
Definition: scp_common.h:80
ScpOpcode opcode
Definition: scp_common.h:79
const char_t * message
Definition: scp_common.h:85
uint64_t size
Definition: scp_common.h:81
Structure describing channel events.
Definition: ssh.h:1560
uint_t eventMask
Requested events.
Definition: ssh.h:1562
SshChannel * channel
Handle to a channel to monitor.
Definition: ssh.h:1561
uint_t eventFlags
Returned events.
Definition: ssh.h:1563
"exec" channel request parameters
Definition: ssh_request.h:119
SshString command
Definition: ssh_request.h:120
String.
Definition: ssh_types.h:56
const char_t * value
Definition: ssh_types.h:57
size_t length
Definition: ssh_types.h:58
uint8_t length
Definition: tcp.h:368