ftp_server_misc.c
Go to the documentation of this file.
1 /**
2  * @file ftp_server_misc.c
3  * @brief Helper functions for FTP server
4  *
5  * @section License
6  *
7  * Copyright (C) 2010-2018 Oryx Embedded SARL. All rights reserved.
8  *
9  * This file is part of CycloneTCP Open.
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24  *
25  * @author Oryx Embedded SARL (www.oryx-embedded.com)
26  * @version 1.9.0
27  **/
28 
29 //Switch to the appropriate trace level
30 #define TRACE_LEVEL FTP_TRACE_LEVEL
31 
32 //Dependencies
33 #include "ftp/ftp_server.h"
34 #include "ftp/ftp_server_events.h"
36 #include "ftp/ftp_server_misc.h"
37 #include "str.h"
38 #include "path.h"
39 #include "error.h"
40 #include "debug.h"
41 
42 //Check TCP/IP stack configuration
43 #if (FTP_SERVER_SUPPORT == ENABLED)
44 
45 
46 /**
47  * @brief Get a passive port number
48  * @param[in] context Pointer to the FTP server context
49  * @return Passive port number
50  **/
51 
53 {
54  uint_t port;
55 
56  //Retrieve current passive port number
57  port = context->passivePort;
58 
59  //Invalid port number?
60  if(port < context->settings.passivePortMin ||
61  port > context->settings.passivePortMax)
62  {
63  //Generate a random port number
64  port = context->settings.passivePortMin + netGetRand() %
65  (context->settings.passivePortMax - context->settings.passivePortMin + 1);
66  }
67 
68  //Next passive port to use
69  if(port < context->settings.passivePortMax)
70  {
71  //Increment port number
72  context->passivePort = port + 1;
73  }
74  else
75  {
76  //Wrap around if necessary
77  context->passivePort = context->settings.passivePortMin;
78  }
79 
80  //Return the passive port number
81  return port;
82 }
83 
84 
85 /**
86  * @brief Close client connection properly
87  * @param[in] context Pointer to the FTP server context
88  * @param[in] connection Pointer to the client connection to be closed
89  **/
90 
92  FtpClientConnection *connection)
93 {
94  uint_t i;
95 
96  //Make sure the connection is active
97  if(connection != NULL)
98  {
99  //Loop through client connection table
100  for(i = 0; i < FTP_SERVER_MAX_CONNECTIONS; i++)
101  {
102  //Search the table for the specified connection
103  if(context->connection[i] == connection)
104  {
105  //Close data connection
106  ftpServerCloseDataConnection(connection);
107  //Close control connection
109 
110  //Release previously allocated resources
111  if(connection->file != NULL)
112  fsCloseFile(connection->file);
113 
114  if(connection->dir != NULL)
115  fsCloseDir(connection->dir);
116 
117  //Free memory
118  memPoolFree(connection->buffer);
119  memPoolFree(connection);
120 
121  //Mark the entry as free
122  context->connection[i] = NULL;
123  //We are done
124  break;
125  }
126  }
127  }
128 }
129 
130 
131 /**
132  * @brief Accept control connection
133  * @param[in] context Pointer to the FTP server context
134  * @return Pointer to the connection
135  **/
136 
138 {
139  error_t error;
140  uint_t i;
141  Socket *socket;
142  IpAddr clientIpAddr;
143  uint16_t clientPort;
144  FtpClientConnection *connection;
145 
146  //Accept incoming connection
147  socket = socketAccept(context->socket, &clientIpAddr, &clientPort);
148  //Failure detected?
149  if(socket == NULL)
150  return NULL;
151 
152  //Force the socket to operate in non-blocking mode
153  error = socketSetTimeout(socket, 0);
154  //Any error to report?
155  if(error)
156  {
157  //Close socket
159  //Exit immediately
160  return NULL;
161  }
162 
163  //Loop through client connection table
164  for(i = 0; i < FTP_SERVER_MAX_CONNECTIONS; i++)
165  {
166  //Check whether the entry is currently in used or not
167  if(context->connection[i] == NULL)
168  {
169  //Allocate resources for the new connection
170  connection = memPoolAlloc(sizeof(FtpClientConnection));
171  //Failed to allocate memory?
172  if(connection == NULL)
173  {
174  //Debug message
175  TRACE_ERROR("FTP server: Failed to allocate memory!\r\n");
176  //Exit immediately
177  break;
178  }
179 
180  //Clear the structure
181  memset(connection, 0, sizeof(FtpClientConnection));
182 
183  //Allocate a memory buffer for I/O operations
185  //Failed to allocate memory
186  if(connection->buffer == NULL)
187  {
188  //Clean up side effects
189  memPoolFree(connection);
190  //Debug message
191  TRACE_ERROR("FTP server: Failed to allocate memory!\r\n");
192  //Exit immediately
193  break;
194  }
195 
196  //Debug message
197  TRACE_INFO("TCP server: Control connection established with client %s port %" PRIu16 "...\r\n",
198  ipAddrToString(&clientIpAddr, NULL), clientPort);
199 
200  //Underlying network interface
201  connection->interface = socket->interface;
202  //Save socket handle
203  connection->controlSocket = socket;
204  //Set home directory
205  strcpy(connection->homeDir, context->settings.rootDir);
206  //Set current directory
207  strcpy(connection->currentDir, context->settings.rootDir);
208 
209  //Format greeting message
210  strcpy(connection->response, "220 Service ready for new user\r\n");
211  //Debug message
212  TRACE_DEBUG("FTP server: %s", connection->response);
213 
214  //Number of bytes in the response buffer
215  connection->responseLength = strlen(connection->response);
216  connection->responsePos = 0;
217 
218  //The client connection is ready for use
219  context->connection[i] = connection;
220  //Successful processing
221  return connection;
222  }
223  }
224 
225  //Debug message
226  TRACE_INFO("TCP server: Connection refused with client %s port %" PRIu16 "...\r\n",
227  ipAddrToString(&clientIpAddr, NULL), clientPort);
228 
229  //Close socket
231  //The FTP server cannot accept the incoming connection request
232  return NULL;
233 }
234 
235 
236 /**
237  * @brief Close control connection
238  * @param[in] connection Pointer to the client connection
239  **/
240 
242 {
243  IpAddr clientIpAddr;
244  uint16_t clientPort;
245 
246  //Any running control connection?
247  if(connection->controlSocket != NULL)
248  {
249  //Retrieve the address of the peer to which a socket is connected
250  socketGetRemoteAddr(connection->controlSocket, &clientIpAddr, &clientPort);
251 
252  //Debug message
253  TRACE_INFO("FTP server: Closing control connection with client %s port %" PRIu16 "...\r\n",
254  ipAddrToString(&clientIpAddr, NULL), clientPort);
255 
256  //Close control connection
257  socketClose(connection->controlSocket);
258  connection->controlSocket = NULL;
259 
260  //Back to idle state
261  connection->controlState = FTP_CONTROL_STATE_IDLE;
262  }
263 }
264 
265 
266 /**
267  * @brief Open data connection
268  * @param[in] context Pointer to the FTP server context
269  * @param[in] connection Pointer to the client connection
270  * @return Error code
271  **/
272 
274  FtpClientConnection *connection)
275 {
276  error_t error;
277 
278  //Release previously allocated resources
279  ftpServerCloseDataConnection(connection);
280 
281  //No port specified?
282  if(!connection->remotePort)
283  return ERROR_FAILURE;
284 
285  //Debug message
286  TRACE_INFO("FTP server: Opening data connection with client %s port %" PRIu16 "...\r\n",
287  ipAddrToString(&connection->remoteIpAddr, NULL), connection->remotePort);
288 
289  //Open data socket
291  //Failed to open socket?
292  if(!connection->dataSocket)
293  return ERROR_OPEN_FAILED;
294 
295  //Start of exception handling block
296  do
297  {
298  //Force the socket to operate in non-blocking mode
299  error = socketSetTimeout(connection->dataSocket, 0);
300  //Any error to report?
301  if(error)
302  break;
303 
304  //Adjust the size of the TX buffer
305  error = socketSetTxBufferSize(connection->dataSocket,
307  //Any error to report?
308  if(error)
309  break;
310 
311  //Adjust the size of the RX buffer
312  error = socketSetRxBufferSize(connection->dataSocket,
314  //Any error to report?
315  if(error)
316  break;
317 
318  //Associate the socket with the relevant interface
319  error = socketBindToInterface(connection->dataSocket, connection->interface);
320  //Unable to bind the socket to the desired interface?
321  if(error)
322  break;
323 
324  //The server initiates the data connection from port 20
325  error = socketBind(connection->dataSocket, &IP_ADDR_ANY,
326  context->settings.dataPort);
327  //Any error to report?
328  if(error)
329  break;
330 
331  //Establish data connection
332  error = socketConnect(connection->dataSocket,
333  &connection->remoteIpAddr, connection->remotePort);
334  //Any error to report?
335  if(error != NO_ERROR && error != ERROR_TIMEOUT)
336  break;
337 
338  //Connection is being established
339  error = NO_ERROR;
340 
341  //End of exception handling block
342  } while(0);
343 
344  //Any error to report?
345  if(error)
346  {
347  //Clean up side effects
348  ftpServerCloseDataConnection(connection);
349  //Exit immediately
350  return error;
351  }
352 
353  //Successful processing
354  return NO_ERROR;
355 }
356 
357 
358 /**
359  * @brief Accept data connection
360  * @param[in] connection Pointer to the client connection
361  **/
362 
364 {
365  error_t error;
366  Socket *socket;
367  IpAddr clientIpAddr;
368  uint16_t clientPort;
369 
370  //Accept incoming connection
371  socket = socketAccept(connection->dataSocket, &clientIpAddr, &clientPort);
372  //Failure detected?
373  if(socket == NULL)
374  return;
375 
376  //Debug message
377  TRACE_INFO("FTP server: Data connection established with client %s port %" PRIu16 "...\r\n",
378  ipAddrToString(&clientIpAddr, NULL), clientPort);
379 
380  //Close the listening socket
381  socketClose(connection->dataSocket);
382  //Save socket handle
383  connection->dataSocket = socket;
384 
385  //Force the socket to operate in non-blocking mode
386  error = socketSetTimeout(connection->dataSocket, 0);
387  //Any error to report?
388  if(error)
389  {
390  //Clean up side effects
391  socketClose(connection->dataSocket);
392  //Exit immediately
393  return;
394  }
395 
396  //Check current state
397  if(connection->controlState == FTP_CONTROL_STATE_LIST ||
398  connection->controlState == FTP_CONTROL_STATE_RETR)
399  {
400  //Prepare to send data
401  connection->dataState = FTP_DATA_STATE_SEND;
402  }
403  else if(connection->controlState == FTP_CONTROL_STATE_STOR ||
404  connection->controlState == FTP_CONTROL_STATE_APPE)
405  {
406  //Prepare to receive data
407  connection->dataState = FTP_DATA_STATE_RECEIVE;
408  }
409  else
410  {
411  //Data transfer direction is unknown...
412  connection->dataState = FTP_DATA_STATE_IDLE;
413  }
414 }
415 
416 
417 /**
418  * @brief Close data connection
419  * @param[in] connection Pointer to the client connection
420  **/
421 
423 {
424  IpAddr clientIpAddr;
425  uint16_t clientPort;
426 
427  //Any running data connection?
428  if(connection->dataSocket != NULL)
429  {
430  //Retrieve the address of the peer to which a socket is connected
431  socketGetRemoteAddr(connection->dataSocket, &clientIpAddr, &clientPort);
432 
433  //Check whether the data connection is established
434  if(clientPort != 0)
435  {
436  //Debug message
437  TRACE_INFO("FTP server: Closing data connection with client %s port %" PRIu16 "...\r\n",
438  ipAddrToString(&clientIpAddr, NULL), clientPort);
439  }
440 
441  //Close data connection
442  socketClose(connection->dataSocket);
443  connection->dataSocket = NULL;
444 
445  //Re initialize data connection
446  connection->passiveMode = FALSE;
447  connection->remotePort = 0;
448 
449  //Back to default state
450  connection->dataState = FTP_DATA_STATE_CLOSED;
451  }
452 }
453 
454 
455 /**
456  * @brief Retrieve the full pathname
457  * @param[in] connection Pointer to the client connection
458  * @param[in] inputPath Relative or absolute path
459  * @param[out] outputPath Resulting full path
460  * @param[in] maxLen Maximum acceptable path length
461  * @return Error code
462  **/
463 
465  const char_t *inputPath, char_t *outputPath, size_t maxLen)
466 {
467  size_t n;
468 
469  //Relative or absolute path?
470  if(pathIsRelative(inputPath))
471  {
472  //Sanity check
473  if(strlen(connection->currentDir) > maxLen)
474  return ERROR_FAILURE;
475 
476  //Copy current directory
477  strcpy(outputPath, connection->currentDir);
478  //Append the specified path
479  pathCombine(outputPath, inputPath, maxLen);
480  }
481  else
482  {
483  //Sanity check
484  if(strlen(connection->homeDir) > maxLen)
485  return ERROR_FAILURE;
486 
487  //Copy home directory
488  strcpy(outputPath, connection->homeDir);
489  //Append the specified path
490  pathCombine(outputPath, inputPath, maxLen);
491  }
492 
493  //Clean the resulting path
494  pathCanonicalize(outputPath);
495  pathRemoveSlash(outputPath);
496 
497  //Calculate the length of the home directory
498  n = strlen(connection->homeDir);
499 
500  //Make sure the pathname is valid
501  if(strncmp(outputPath, connection->homeDir, n))
502  return ERROR_INVALID_PATH;
503 
504  //Successful processing
505  return NO_ERROR;
506 }
507 
508 
509 /**
510  * @brief Get permissions for the specified file or directory
511  * @param[in] context Pointer to the FTP server context
512  * @param[in] connection Pointer to the client connection
513  * @param[in] path Canonical path of the file
514  * @return Access rights for the specified file
515  **/
516 
518  FtpClientConnection *connection, const char_t *path)
519 {
520  size_t n;
521  uint_t perm;
522 
523  //Calculate the length of the home directory
524  n = strlen(connection->homeDir);
525 
526  //Make sure the pathname is valid
527  if(!strncmp(path, connection->homeDir, n))
528  {
529  //Strip root directory from the pathname
530  path = ftpServerStripRootDir(context, path);
531 
532  //Invoke user-defined callback, if any
533  if(context->settings.getFilePermCallback != NULL)
534  {
535  //Retrieve access rights for the specified file
536  perm = context->settings.getFilePermCallback(connection, connection->user, path);
537  }
538  else
539  {
540  //Use default access rights
542  }
543  }
544  else
545  {
546  //The specified pathname is not valid
547  perm = 0;
548  }
549 
550  //Return access rights
551  return perm;
552 }
553 
554 
555 /**
556  * @brief Strip root dir from specified pathname
557  * @param[in] context Pointer to the FTP server context
558  * @param[in] path input pathname
559  * @return Resulting pathname with root dir stripped
560  **/
561 
563 {
564  //Default directory
565  static const char_t defaultDir[] = "/";
566 
567  //Local variables
568  size_t m;
569  size_t n;
570 
571  //Retrieve the length of the root directory
572  n = strlen(context->settings.rootDir);
573  //Retrieve the length of the specified pathname
574  m = strlen(path);
575 
576  //Strip the root dir from the specified pathname
577  if(n <= 1)
578  return path;
579  else if(n < m)
580  return path + n;
581  else
582  return defaultDir;
583 }
584 
585 
586 /**
587  * @brief Strip home directory from specified pathname
588  * @param[in] connection Pointer to the client connection
589  * @param[in] path input pathname
590  * @return Resulting pathname with home directory stripped
591  **/
592 
593 const char_t *ftpServerStripHomeDir(FtpClientConnection *connection, const char_t *path)
594 {
595  //Default directory
596  static const char_t defaultDir[] = "/";
597 
598  //Local variables
599  size_t m;
600  size_t n;
601 
602  //Retrieve the length of the home directory
603  n = strlen(connection->homeDir);
604  //Retrieve the length of the specified pathname
605  m = strlen(path);
606 
607  //Strip the home directory from the specified pathname
608  if(n <= 1)
609  return path;
610  else if(n < m)
611  return path + n;
612  else
613  return defaultDir;
614 }
615 
616 #endif
char_t currentDir[FTP_SERVER_MAX_PATH_LEN+1]
Current directory.
Definition: ftp_server.h:252
size_t responsePos
Current position in the response buffer.
Definition: ftp_server.h:258
void ftpServerCloseDataConnection(FtpClientConnection *connection)
Close data connection.
error_t ftpServerOpenDataConnection(FtpServerContext *context, FtpClientConnection *connection)
Open data connection.
Helper functions for FTP server.
char char_t
Definition: compiler_port.h:41
Socket * socket
Listening socket.
Definition: ftp_server.h:325
#define FTP_SERVER_BUFFER_SIZE
Definition: ftp_server.h:93
void ftpServerCloseConnection(FtpServerContext *context, FtpClientConnection *connection)
Close client connection properly.
Debugging facilities.
Socket * controlSocket
Control connection socket.
Definition: ftp_server.h:242
FtpControlConnState controlState
Control connection state.
Definition: ftp_server.h:241
Generic error code.
Definition: error.h:43
Socket * socketAccept(Socket *socket, IpAddr *clientIpAddr, uint16_t *clientPort)
Permit an incoming connection attempt on a socket.
Definition: socket.c:457
error_t ftpServerGetPath(FtpClientConnection *connection, const char_t *inputPath, char_t *outputPath, size_t maxLen)
Retrieve the full pathname.
IP network address.
Definition: ip.h:57
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:797
#define TRACE_ERROR(...)
Definition: debug.h:70
void * memPoolAlloc(size_t size)
Allocate a memory block.
Definition: net_mem.c:98
char_t * ipAddrToString(const IpAddr *ipAddr, char_t *str)
Convert a binary IP address to a string representation.
Definition: ip.c:685
String manipulation helper functions.
const IpAddr IP_ADDR_ANY
Definition: ip.c:42
void pathCombine(char_t *path, const char_t *more, size_t maxLen)
Concatenate two paths.
Definition: path.c:330
uint8_t m
Definition: ndp.h:299
FTP server (command processing)
char_t user[FTP_SERVER_MAX_USERNAME_LEN+1]
User name.
Definition: ftp_server.h:250
int_t socket(int_t family, int_t type, int_t protocol)
Create a socket that is bound to a specific transport service provider.
Definition: bsd_socket.c:106
FTP server context.
Definition: ftp_server.h:321
uint_t ftpServerGetFilePermissions(FtpServerContext *context, FtpClientConnection *connection, const char_t *path)
Get permissions for the specified file or directory.
Error codes description.
FtpClientConnection * connection[FTP_SERVER_MAX_CONNECTIONS]
Client connections.
Definition: ftp_server.h:327
char_t response[FTP_SERVER_MAX_LINE_LEN+1]
Response buffer.
Definition: ftp_server.h:256
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:216
#define Socket
Definition: socket.h:34
#define FTP_SERVER_DATA_SOCKET_BUFFER_SIZE
Definition: ftp_server.h:135
FtpDataConnState dataState
Data connection state.
Definition: ftp_server.h:243
error_t socketSetRxBufferSize(Socket *socket, size_t size)
Specify the size of the receive buffer.
Definition: socket.c:275
void pathRemoveSlash(char_t *path)
Remove the trailing slash from a given path.
Definition: path.c:300
uint32_t netGetRand(void)
Get a random value.
Definition: net.c:1523
size_t responseLength
Number of bytes available in the response buffer.
Definition: ftp_server.h:257
uint16_t remotePort
Remote port number.
Definition: ftp_server.h:249
void ftpServerAcceptDataConnection(FtpClientConnection *connection)
Accept data connection.
void memPoolFree(void *p)
Release a memory block.
Definition: net_mem.c:164
void pathCanonicalize(char_t *path)
Simplify a path.
Definition: path.c:110
void fsCloseDir(FsDir *dir)
Close a directory stream.
char_t rootDir[FTP_SERVER_MAX_ROOT_DIR_LEN+1]
Root directory.
Definition: ftp_server.h:309
bool_t pathIsRelative(const char_t *path)
Test if the path is relative.
Definition: path.c:55
FtpClientConnection * ftpServerAcceptControlConnection(FtpServerContext *context)
Accept control connection.
error_t socketGetRemoteAddr(Socket *socket, IpAddr *remoteIpAddr, uint16_t *remotePort)
Retrieve the address of the peer to which a socket is connected.
Definition: socket.c:730
error_t socketSetTxBufferSize(Socket *socket, size_t size)
Specify the size of the send buffer.
Definition: socket.c:241
void ftpServerCloseControlConnection(FtpClientConnection *connection)
Close control connection.
bool_t passiveMode
Passive data transfer.
Definition: ftp_server.h:247
FtpGetFilePermCallback getFilePermCallback
Callback used to retrieve file permissions.
Definition: ftp_server.h:312
#define FTP_SERVER_MAX_CONNECTIONS
Definition: ftp_server.h:58
#define TRACE_INFO(...)
Definition: debug.h:86
Success.
Definition: error.h:42
Path manipulation helper functions.
const char_t * ftpServerStripRootDir(FtpServerContext *context, const char_t *path)
Strip root dir from specified pathname.
IpAddr remoteIpAddr
Remote IP address.
Definition: ftp_server.h:248
uint16_t ftpServerGetPassivePort(FtpServerContext *context)
Get a passive port number.
error_t socketConnect(Socket *socket, const IpAddr *remoteIpAddr, uint16_t remotePort)
Establish a connection to a specified socket.
Definition: socket.c:357
error_t
Error codes.
Definition: error.h:40
unsigned int uint_t
Definition: compiler_port.h:43
Socket * socketOpen(uint_t type, uint_t protocol)
Create a socket (UDP or TCP)
Definition: socket.c:92
const char_t * ftpServerStripHomeDir(FtpClientConnection *connection, const char_t *path)
Strip home directory from specified pathname.
FsDir * dir
Directory pointer.
Definition: ftp_server.h:246
uint16_t passivePortMax
Passive port range (upper value)
Definition: ftp_server.h:307
FTP server (event handlers)
Socket * dataSocket
Data connection socket.
Definition: ftp_server.h:244
char_t * buffer
Memory buffer for I/O operations.
Definition: ftp_server.h:259
uint16_t port
Definition: dns_common.h:221
FtpServerSettings settings
User settings.
Definition: ftp_server.h:323
uint16_t passivePort
Current passive port number.
Definition: ftp_server.h:326
error_t socketBind(Socket *socket, const IpAddr *localIpAddr, uint16_t localPort)
Associate a local address with a socket.
Definition: socket.c:331
FsFile * file
File pointer.
Definition: ftp_server.h:245
uint8_t n
uint16_t dataPort
FTP data port number.
Definition: ftp_server.h:305
char_t homeDir[FTP_SERVER_MAX_HOME_DIR_LEN+1]
Home directory.
Definition: ftp_server.h:251
#define FALSE
Definition: os_port.h:44
FTP server (File Transfer Protocol)
NetInterface * interface
Underlying network interface.
Definition: ftp_server.h:238
uint16_t passivePortMin
Passive port range (lower value)
Definition: ftp_server.h:306
error_t socketBindToInterface(Socket *socket, NetInterface *interface)
Bind a socket to a particular network interface.
Definition: socket.c:309
FTP client connection.
Definition: ftp_server.h:236
void fsCloseFile(FsFile *file)
Close a file.
#define TRACE_DEBUG(...)
Definition: debug.h:98