Kea 3.0.0
filesystem.cc
Go to the documentation of this file.
1// Copyright (C) 2021-2025 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
10#include <util/filesystem.h>
11#include <util/str.h>
12
13#include <cstdio>
14#include <cstdlib>
15#include <fstream>
16#include <string>
17#include <iostream>
18
19#include <dirent.h>
20#include <fcntl.h>
21
22using namespace isc;
23using namespace isc::util::str;
24using namespace std;
25
26namespace isc {
27namespace util {
28namespace file {
29
30
31string
32getContent(string const& file_name) {
33 if (!exists(file_name)) {
34 isc_throw(BadValue, "Expected a file at path '" << file_name << "'");
35 }
36 if (!isFile(file_name)) {
37 isc_throw(BadValue, "Expected '" << file_name << "' to be a regular file");
38 }
39 ifstream file(file_name, ios::in);
40 if (!file.is_open()) {
41 isc_throw(BadValue, "Cannot open '" << file_name);
42 }
43 string content;
44 file >> content;
45 return (content);
46}
47
48bool
49exists(string const& path) {
50 struct stat statbuf;
51 return (::stat(path.c_str(), &statbuf) == 0);
52}
53
54mode_t
55getPermissions(const std::string path) {
56 struct stat statbuf;
57 if (::stat(path.c_str(), &statbuf) < 0) {
58 return (0);
59 }
60
61 return (statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
62}
63
64bool
65hasPermissions(const std::string path, const mode_t& permissions) {
66 return (getPermissions(path) == permissions);
67}
68
69bool
70isDir(string const& path) {
71 struct stat statbuf;
72 if (::stat(path.c_str(), &statbuf) < 0) {
73 return (false);
74 }
75 return ((statbuf.st_mode & S_IFMT) == S_IFDIR);
76}
77
78bool
79isFile(string const& path) {
80 struct stat statbuf;
81 if (::stat(path.c_str(), &statbuf) < 0) {
82 return (false);
83 }
84 return ((statbuf.st_mode & S_IFMT) == S_IFREG);
85}
86
87bool
88isSocket(string const& path) {
89 struct stat statbuf;
90 if (::stat(path.c_str(), &statbuf) < 0) {
91 return (false);
92 }
93 return ((statbuf.st_mode & S_IFMT) == S_IFSOCK);
94}
95
96void
98 // No group write and no other access.
99 mode_t mask(S_IWGRP | S_IRWXO);
100 mode_t orig = umask(mask);
101 // Handle the case where the original umask was already more restrictive.
102 if ((orig | mask) != mask) {
103 static_cast<void>(umask(orig | mask));
104 }
105}
106
107Path::Path(string const& full_name) {
108 dir_present_ = false;
109 if (!full_name.empty()) {
110 // Find the directory.
111 size_t last_slash = full_name.find_last_of('/');
112 if (last_slash != string::npos) {
113 // Found a directory so note the fact.
114 dir_present_ = true;
115
116 // Found the last slash, so extract directory component and
117 // set where the scan for the last_dot should terminate.
118 parent_path_ = full_name.substr(0, last_slash);
119 if (last_slash == full_name.size()) {
120 // The entire string was a directory, so exit and don't
121 // do any more searching.
122 return;
123 }
124 }
125
126 // Now search backwards for the last ".".
127 size_t last_dot = full_name.find_last_of('.');
128 if ((last_dot == string::npos) || (dir_present_ && (last_dot < last_slash))) {
129 // Last "." either not found or it occurs to the left of the last
130 // slash if a directory was present (so it is part of a directory
131 // name). In this case, the remainder of the string after the slash
132 // is the name part.
133 stem_ = full_name.substr(last_slash + 1);
134 return;
135 }
136
137 // Did find a valid dot, so it and everything to the right is the
138 // extension...
139 extension_ = full_name.substr(last_dot);
140
141 // ... and the name of the file is everything in between.
142 if ((last_dot - last_slash) > 1) {
143 stem_ = full_name.substr(last_slash + 1, last_dot - last_slash - 1);
144 }
145 }
146}
147
148string
149Path::str() const {
150 return (parent_path_ + (dir_present_ ? "/" : "") + stem_ + extension_);
151}
152
153string
155 return (parent_path_);
156}
157
158string
160 return (parent_path_ + (dir_present_ ? "/" : ""));
161}
162
163string
164Path::stem() const {
165 return (stem_);
166}
167
168string
170 return (extension_);
171}
172
173string
175 return (stem_ + extension_);
176}
177
178Path&
179Path::replaceExtension(string const& replacement) {
180 string const trimmed_replacement(trim(replacement));
181 if (trimmed_replacement.empty()) {
182 extension_ = string();
183 } else {
184 size_t const last_dot(trimmed_replacement.find_last_of('.'));
185 if (last_dot == string::npos) {
186 extension_ = "." + trimmed_replacement;
187 } else {
188 extension_ = trimmed_replacement.substr(last_dot);
189 }
190 }
191 return (*this);
192}
193
194Path&
195Path::replaceParentPath(string const& replacement) {
196 string const trimmed_replacement(trim(replacement));
197 dir_present_ = (trimmed_replacement.find_last_of('/') != string::npos);
198 if (trimmed_replacement.empty() || (trimmed_replacement == "/")) {
199 parent_path_ = string();
200 } else if (trimmed_replacement.at(trimmed_replacement.size() - 1) == '/') {
201 parent_path_ = trimmed_replacement.substr(0, trimmed_replacement.size() - 1);
202 } else {
203 parent_path_ = trimmed_replacement;
204 }
205 return (*this);
206}
207
209 char dir[]("/tmp/kea-tmpdir-XXXXXX");
210 char const* dir_name = mkdtemp(dir);
211 if (!dir_name) {
212 isc_throw(Unexpected, "mkdtemp failed " << dir << ": " << strerror(errno));
213 }
214 dir_name_ = string(dir_name);
215}
216
218 DIR *dir(opendir(dir_name_.c_str()));
219 if (!dir) {
220 return;
221 }
222
223 std::unique_ptr<DIR, void(*)(DIR*)> defer(dir, [](DIR* d) { closedir(d); });
224
225 struct dirent *i;
226 string filepath;
227 while ((i = readdir(dir))) {
228 if (strcmp(i->d_name, ".") == 0 || strcmp(i->d_name, "..") == 0) {
229 continue;
230 }
231
232 filepath = dir_name_ + '/' + i->d_name;
233 remove(filepath.c_str());
234 }
235
236 rmdir(dir_name_.c_str());
237}
238
240 return dir_name_;
241}
242
243PathChecker::PathChecker(const std::string default_path,
244 const std::string env_name /* = "" */)
245 : default_path_(default_path), env_name_(env_name),
246 default_overridden_(false) {
247 getPath(true);
248}
249
250std::string
251PathChecker::getPath(bool reset /* = false */,
252 const std::string explicit_path /* = "" */) {
253 if (reset) {
254 if (!explicit_path.empty()) {
255 path_ = explicit_path;
256 } else if (!env_name_.empty()) {
257 path_ = std::string(std::getenv(env_name_.c_str()) ?
258 std::getenv(env_name_.c_str()) : default_path_);
259 } else {
260 path_ = default_path_;
261 }
262
263 // Remove the trailing "/" if it is present so comparison to
264 // other Path::parentPath() works.
265 while (!path_.empty() && path_.back() == '/') {
266 path_.pop_back();
267 }
268
269 default_overridden_ = (path_ != default_path_);
270 }
271
272 return (path_);
273}
274
275std::string
276PathChecker::validatePath(const std::string input_path_str,
277 bool enforce_path /* = PathChecker::shouldEnforceSecurity() */) const {
278 Path input_path(trim(input_path_str));
279 auto filename = input_path.filename();
280 if (filename.empty()) {
281 isc_throw(BadValue, "path: '" << input_path.str() << "' has no filename");
282 }
283
284 auto parent_path = input_path.parentPath();
285 auto parent_dir = input_path.parentDirectory();
286 if (!parent_dir.empty()) {
287 if (!enforce_path) {
288 // Security set to lax, let it fly.
289 return (input_path_str);
290 }
291
292 // We only allow absolute path equal to default. Catch an invalid path.
293 if ((parent_path != path_) || (parent_dir == "/")) {
294 isc_throw(BadValue, "invalid path specified: '"
295 << (parent_path.empty() ? "/" : parent_path)
296 << "', supported path is '"
297 << path_ << "'");
298 }
299 }
300
301 std::string valid_path(path_ + "/" + filename);
302 return (valid_path);
303}
304
305std::string
306PathChecker::validateDirectory(const std::string input_path_str,
307 bool enforce_path /* = PathChecker::shouldEnforceSecurity() */) const {
308 std::string input_copy = trim(input_path_str);
309 if (!enforce_path) {
310 return(input_copy);
311 }
312
313 // We only allow absolute path equal to default. Catch an invalid path.
314 if (!input_path_str.empty()) {
315 std::string input_copy = input_path_str;
316 while (!input_copy.empty() && input_copy.back() == '/') {
317 input_copy.pop_back();
318 }
319
320 if (input_copy != path_) {
321 isc_throw(BadValue, "invalid path specified: '"
322 << input_path_str << "', supported path is '"
323 << path_ << "'");
324 }
325 }
326
327 return (path_);
328}
329
330bool
331PathChecker::pathHasPermissions(mode_t permissions, bool enforce_perms
332 /* = PathChecker::shouldEnforceSecurity() */) const {
333 return((!enforce_perms) || hasPermissions(path_, permissions));
334}
335
336bool
338 return (default_overridden_);
339}
340
342 return (enforce_security_);
343}
344
346 enforce_security_ = enable;
347}
348
349bool PathChecker::enforce_security_ = true;
350
351} // namespace file
352} // namespace util
353} // namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
A generic exception that is thrown when an unexpected error condition occurs.
std::string getPath(bool reset=false, const std::string explicit_path="")
Fetches the supported path.
static bool shouldEnforceSecurity()
Indicates security checks should be enforced.
PathChecker(const std::string default_path, const std::string env_name="")
Constructor.
bool isDefaultOverridden()
Indicates if the default path has been overridden.
static void enableEnforcement(bool enable)
Enables or disables security enforcment checks.
std::string validateDirectory(const std::string input_path_str, bool enforce_path=shouldEnforceSecurity()) const
Validates a directory against a supported path.
bool pathHasPermissions(mode_t permissions, bool enforce_perms=shouldEnforceSecurity()) const
Check if the path has expected permissions.
std::string validatePath(const std::string input_path_str, bool enforce_path=shouldEnforceSecurity()) const
Validates a file path against a supported path.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
bool isSocket(string const &path)
Check if there is a socket at the given path.
Definition filesystem.cc:88
string getContent(string const &file_name)
Get the content of a regular file.
Definition filesystem.cc:32
bool isFile(string const &path)
Check if there is a file at the given path.
Definition filesystem.cc:79
bool exists(string const &path)
Check if there is a file or directory at the given path.
Definition filesystem.cc:49
bool isDir(string const &path)
Check if there is a directory at the given path.
Definition filesystem.cc:70
mode_t getPermissions(const std::string path)
Fetches the file permissions mask.
Definition filesystem.cc:55
bool hasPermissions(const std::string path, const mode_t &permissions)
Check if there if file or directory has the given permissions.
Definition filesystem.cc:65
void setUmask()
Set umask (at least 0027 i.e. no group write and no other access).
Definition filesystem.cc:97
string trim(const string &input)
Trim leading and trailing spaces.
Definition str.cc:32
Defines the logger used by the top-level component of kea-lfc.
Paths on a filesystem.
Definition filesystem.h:83
Path(std::string const &path)
Constructor.
Path & replaceParentPath(std::string const &replacement=std::string())
Trims {replacement} and replaces this instance's parent path with it.
std::string parentDirectory() const
Get the parent directory.
std::string extension() const
Get the extension of the file.
Path & replaceExtension(std::string const &replacement=std::string())
Identifies the extension in {replacement}, trims it, and replaces this instance's extension with it.
std::string stem() const
Get the base name of the file without the extension.
std::string parentPath() const
Get the parent path.
std::string filename() const
Get the name of the file, extension included.
std::string str() const
Get the path in textual format.