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  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2010-2019 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneTCP 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 1.9.6
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL FTP_TRACE_LEVEL
33 
34 //Dependencies
35 #include "ftp/ftp_server.h"
36 #include "ftp/ftp_server_control.h"
37 #include "ftp/ftp_server_data.h"
38 #include "ftp/ftp_server_misc.h"
39 #include "path.h"
40 #include "debug.h"
41 
42 //Check TCP/IP stack configuration
43 #if (FTP_SERVER_SUPPORT == ENABLED)
44 
45 
46 /**
47  * @brief Handle periodic operations
48  * @param[in] context Pointer to the FTP server context
49  **/
50 
52 {
53  uint_t i;
55  FtpClientConnection *connection;
56 
57  //Get current time
59 
60  //Loop through the connection table
61  for(i = 0; i < context->settings.maxConnections; i++)
62  {
63  //Point to the current entry
64  connection = &context->connections[i];
65 
66  //Check the state of the current connection
67  if(connection->controlChannel.state != FTP_CHANNEL_STATE_CLOSED)
68  {
69  //Disconnect inactive client after idle timeout
70  if(timeCompare(time, connection->timestamp + FTP_SERVER_TIMEOUT) >= 0)
71  {
72  //Debug message
73  TRACE_INFO("FTP server: Closing inactive connection...\r\n");
74  //Close connection with the client
75  ftpServerCloseConnection(connection);
76  }
77  }
78  }
79 }
80 
81 
82 /**
83  * @brief Get a passive port number
84  * @param[in] context Pointer to the FTP server context
85  * @return Passive port number
86  **/
87 
89 {
90  uint_t port;
91 
92  //Retrieve current passive port number
93  port = context->passivePort;
94 
95  //Invalid port number?
96  if(port < context->settings.passivePortMin ||
97  port > context->settings.passivePortMax)
98  {
99  //Generate a random port number
100  port = context->settings.passivePortMin + netGetRand() %
101  (context->settings.passivePortMax - context->settings.passivePortMin + 1);
102  }
103 
104  //Next passive port to use
105  if(port < context->settings.passivePortMax)
106  {
107  //Increment port number
108  context->passivePort = port + 1;
109  }
110  else
111  {
112  //Wrap around if necessary
113  context->passivePort = context->settings.passivePortMin;
114  }
115 
116  //Return the passive port number
117  return port;
118 }
119 
120 
121 /**
122  * @brief Retrieve the full pathname
123  * @param[in] connection Pointer to the client connection
124  * @param[in] inputPath Relative or absolute path
125  * @param[out] outputPath Resulting full path
126  * @param[in] maxLen Maximum acceptable path length
127  * @return Error code
128  **/
129 
131  const char_t *inputPath, char_t *outputPath, size_t maxLen)
132 {
133  size_t n;
134 
135  //Relative or absolute path?
136  if(pathIsRelative(inputPath))
137  {
138  //Sanity check
139  if(strlen(connection->currentDir) > maxLen)
140  return ERROR_FAILURE;
141 
142  //Copy current directory
143  strcpy(outputPath, connection->currentDir);
144  //Append the specified path
145  pathCombine(outputPath, inputPath, maxLen);
146  }
147  else
148  {
149  //Sanity check
150  if(strlen(connection->homeDir) > maxLen)
151  return ERROR_FAILURE;
152 
153  //Copy home directory
154  strcpy(outputPath, connection->homeDir);
155  //Append the specified path
156  pathCombine(outputPath, inputPath, maxLen);
157  }
158 
159  //Clean the resulting path
160  pathCanonicalize(outputPath);
161  pathRemoveSlash(outputPath);
162 
163  //Calculate the length of the home directory
164  n = strlen(connection->homeDir);
165 
166  //Make sure the pathname is valid
167  if(strncmp(outputPath, connection->homeDir, n))
168  return ERROR_INVALID_PATH;
169 
170  //Successful processing
171  return NO_ERROR;
172 }
173 
174 
175 /**
176  * @brief Get permissions for the specified file or directory
177  * @param[in] connection Pointer to the client connection
178  * @param[in] path Canonical path of the file
179  * @return Access rights for the specified file
180  **/
181 
183  const char_t *path)
184 {
185  size_t n;
186  uint_t perm;
187  FtpServerContext *context;
188 
189  //Point to the FTP server context
190  context = connection->context;
191 
192  //Calculate the length of the home directory
193  n = strlen(connection->homeDir);
194 
195  //Make sure the pathname is valid
196  if(!strncmp(path, connection->homeDir, n))
197  {
198  //Strip root directory from the pathname
199  path = ftpServerStripRootDir(context, path);
200 
201  //Invoke user-defined callback, if any
202  if(context->settings.getFilePermCallback != NULL)
203  {
204  //Retrieve access rights for the specified file
205  perm = context->settings.getFilePermCallback(connection,
206  connection->user, path);
207  }
208  else
209  {
210  //Use default access rights
212  }
213  }
214  else
215  {
216  //The specified pathname is not valid
217  perm = 0;
218  }
219 
220  //Return access rights
221  return perm;
222 }
223 
224 
225 /**
226  * @brief Format a directory entry in UNIX-style format
227  * @param[in] dirEntry Pointer to the directory entry
228  * @param[in] perm Access rights for the specified file
229  * @param[out] buffer Buffer where to format the directory entry
230  * @return Length of resulting string, in bytes
231  **/
232 
233 size_t ftpServerFormatDirEntry(const FsDirEntry *dirEntry, uint_t perm,
234  char_t *buffer)
235 {
236  size_t n;
237  time_t time;
238  time_t modified;
239 
240  //Abbreviated months
241  static const char months[13][4] =
242  {
243  " ",
244  "Jan",
245  "Feb",
246  "Mar",
247  "Apr",
248  "May",
249  "Jun",
250  "Jul",
251  "Aug",
252  "Sep",
253  "Oct",
254  "Nov",
255  "Dec"
256  };
257 
258  //Format links, owner, group and size fields
259  n = sprintf(buffer, "---------- 1 owner group %10" PRIu32,
260  dirEntry->size);
261 
262  //Check whether the current entry is a directory
263  if(dirEntry->attributes & FS_FILE_ATTR_DIRECTORY)
264  {
265  buffer[0] = 'd';
266  }
267 
268  //Read access permitted?
269  if(perm & FTP_FILE_PERM_READ)
270  {
271  buffer[1] = 'r';
272  buffer[4] = 'r';
273  buffer[7] = 'r';
274  }
275 
276  //Write access permitted?
277  if(perm & FTP_FILE_PERM_WRITE)
278  {
279  //Make sure the file is not marked as read-only
280  if(!(dirEntry->attributes & FS_FILE_ATTR_READ_ONLY))
281  {
282  buffer[2] = 'w';
283  buffer[5] = 'w';
284  buffer[8] = 'w';
285  }
286  }
287 
288  //Get current time
290  //Get modification time
291  modified = convertDateToUnixTime(&dirEntry->modified);
292 
293  //Check whether the modification time is within the previous 180 days
294  if(time > modified && time < (modified + FTP_SERVER_180_DAYS))
295  {
296  //The format of the date/time field is Mmm dd hh:mm
297  n += sprintf(buffer + n, " %s %02" PRIu8 " %02" PRIu8 ":%02" PRIu8,
298  months[MIN(dirEntry->modified.month, 12)], dirEntry->modified.day,
299  dirEntry->modified.hours, dirEntry->modified.minutes);
300  }
301  else
302  {
303  //The format of the date/time field is Mmm dd yyyy
304  n += sprintf(buffer + n, " %s %02" PRIu8 " %04" PRIu16,
305  months[MIN(dirEntry->modified.month, 12)], dirEntry->modified.day,
306  dirEntry->modified.year);
307  }
308 
309  //Append filename
310  n += sprintf(buffer + n, " %s\r\n", dirEntry->name);
311 
312  //Return the length of the resulting string, in bytes
313  return n;
314 }
315 
316 
317 /**
318  * @brief Strip root dir from specified pathname
319  * @param[in] context Pointer to the FTP server context
320  * @param[in] path input pathname
321  * @return Resulting pathname with root dir stripped
322  **/
323 
325  const char_t *path)
326 {
327  //Default directory
328  static const char_t defaultDir[] = "/";
329 
330  //Local variables
331  size_t m;
332  size_t n;
333 
334  //Retrieve the length of the root directory
335  n = strlen(context->settings.rootDir);
336  //Retrieve the length of the specified pathname
337  m = strlen(path);
338 
339  //Strip the root dir from the specified pathname
340  if(n <= 1)
341  return path;
342  else if(n < m)
343  return path + n;
344  else
345  return defaultDir;
346 }
347 
348 
349 /**
350  * @brief Strip home directory from specified pathname
351  * @param[in] connection Pointer to the client connection
352  * @param[in] path input pathname
353  * @return Resulting pathname with home directory stripped
354  **/
355 
357  const char_t *path)
358 {
359  //Default directory
360  static const char_t defaultDir[] = "/";
361 
362  //Local variables
363  size_t m;
364  size_t n;
365 
366  //Retrieve the length of the home directory
367  n = strlen(connection->homeDir);
368  //Retrieve the length of the specified pathname
369  m = strlen(path);
370 
371  //Strip the home directory from the specified pathname
372  if(n <= 1)
373  return path;
374  else if(n < m)
375  return path + n;
376  else
377  return defaultDir;
378 }
379 
380 
381 /**
382  * @brief Close client connection properly
383  * @param[in] connection Pointer to the client connection to be closed
384  **/
385 
387 {
388  //Close data connection
389  ftpServerCloseDataChannel(connection);
390  //Close control connection
391  ftpServerCloseControlChannel(connection);
392 
393  //Valid file pointer?
394  if(connection->file != NULL)
395  {
396  //Close file
397  fsCloseFile(connection->file);
398  connection->file = NULL;
399  }
400 
401  //Valid directory pointer?
402  if(connection->dir != NULL)
403  {
404  //Close directory
405  fsCloseDir(connection->dir);
406  connection->dir = NULL;
407  }
408 }
409 
410 #endif
#define FtpServerContext
Definition: ftp_server.h:203
Path manipulation helper functions.
const char_t * ftpServerStripRootDir(FtpServerContext *context, const char_t *path)
Strip root dir from specified pathname.
const char_t * ftpServerStripHomeDir(FtpClientConnection *connection, const char_t *path)
Strip home directory from specified pathname.
FTP data connection.
size_t ftpServerFormatDirEntry(const FsDirEntry *dirEntry, uint_t perm, char_t *buffer)
Format a directory entry in UNIX-style format.
time_t convertDateToUnixTime(const DateTime *date)
Convert date to Unix timestamp.
Definition: date_time.c:258
uint16_t year
Definition: date_time.h:48
bool_t pathIsRelative(const char_t *path)
Test if the path is relative.
Definition: path.c:57
#define timeCompare(t1, t2)
Definition: os_port.h:42
Helper functions for FTP server.
void ftpServerTick(FtpServerContext *context)
Handle periodic operations.
uint8_t day
Definition: date_time.h:50
@ FTP_CHANNEL_STATE_CLOSED
Definition: ftp_server.h:221
@ FS_FILE_ATTR_DIRECTORY
Definition: fs_port.h:61
void pathCanonicalize(char_t *path)
Simplify a path.
Definition: path.c:112
uint8_t minutes
Definition: date_time.h:53
error_t
Error codes.
Definition: error.h:42
void ftpServerCloseDataChannel(FtpClientConnection *connection)
Close data connection.
void fsCloseFile(FsFile *file)
Close a file.
@ ERROR_FAILURE
Generic error code.
Definition: error.h:45
error_t ftpServerGetPath(FtpClientConnection *connection, const char_t *inputPath, char_t *outputPath, size_t maxLen)
Retrieve the full pathname.
void ftpServerCloseConnection(FtpClientConnection *connection)
Close client connection properly.
#define FTP_SERVER_TIMEOUT
Definition: ftp_server.h:74
uint8_t hours
Definition: date_time.h:52
#define TRACE_INFO(...)
Definition: debug.h:94
#define MIN(a, b)
Definition: os_port.h:62
#define FTP_SERVER_180_DAYS
@ ERROR_INVALID_PATH
Definition: error.h:144
uint32_t netGetRand(void)
Get a random value.
Definition: net.c:1810
@ FTP_FILE_PERM_LIST
Definition: ftp_server.h:273
uint16_t port
Definition: dns_common.h:223
char_t name[FS_MAX_NAME_LEN+1]
Definition: fs_port.h:100
uint8_t month
Definition: date_time.h:49
char char_t
Definition: compiler_port.h:43
uint32_t time
DateTime modified
Definition: fs_port.h:99
uint_t ftpServerGetFilePermissions(FtpClientConnection *connection, const char_t *path)
Get permissions for the specified file or directory.
uint8_t m
Definition: ndp.h:302
uint8_t n
Directory entry.
Definition: fs_port.h:95
uint32_t attributes
Definition: fs_port.h:97
uint32_t size
Definition: fs_port.h:98
void ftpServerCloseControlChannel(FtpClientConnection *connection)
Close control connection.
FTP server (File Transfer Protocol)
time_t getCurrentUnixTime(void)
Get current time.
Definition: date_time.c:180
#define FtpClientConnection
Definition: ftp_server.h:207
void pathRemoveSlash(char_t *path)
Remove the trailing slash from a given path.
Definition: path.c:302
@ FTP_FILE_PERM_READ
Definition: ftp_server.h:274
void fsCloseDir(FsDir *dir)
Close a directory stream.
unsigned int uint_t
Definition: compiler_port.h:45
@ FS_FILE_ATTR_READ_ONLY
Definition: fs_port.h:57
uint16_t ftpServerGetPassivePort(FtpServerContext *context)
Get a passive port number.
FTP control connection.
@ FTP_FILE_PERM_WRITE
Definition: ftp_server.h:275
uint32_t systime_t
Definition: compiler_port.h:46
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.
void pathCombine(char_t *path, const char_t *more, size_t maxLen)
Concatenate two paths.
Definition: path.c:332
systime_t osGetSystemTime(void)
Retrieve system time.