OpenVPN 3 Core Library
Loading...
Searching...
No Matches
merge.hpp
Go to the documentation of this file.
1// OpenVPN -- An application to securely tunnel IP networks
2// over a single port, with support for SSL/TLS-based
3// session authentication and key exchange,
4// packet encryption, packet authentication, and
5// packet compression.
6//
7// Copyright (C) 2012- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12// This class will read a standard OpenVPN config file that might contain
13// references to other files, and it will merge the included files into the
14// config file, using inline configuration syntax, to produce a single,
15// unified config file.
16
17#ifndef OPENVPN_OPTIONS_MERGE_H
18#define OPENVPN_OPTIONS_MERGE_H
19
20#include <string>
21#include <sstream>
22#include <vector>
23
32
33namespace openvpn {
34
36{
37 // internal flags
38 enum
39 {
41 F_PKCS12 = (1 << 1),
42 F_HTTP_PROXY = (1 << 2),
43 };
44
45 // limits
46 enum
47 {
49 };
50
51 public:
52 OPENVPN_EXCEPTION(merge_error);
53
54 // public status values
65
66 // merge status
67 Status status() const
68 {
69 return status_;
70 }
71 const std::string &error() const
72 {
73 return error_;
74 }
75
76 // merge path basename
77 const std::string &basename() const
78 {
79 return basename_;
80 }
81
82 // final unified profile
83 const std::string &profile_content() const
84 {
85 return profile_content_;
86 }
87
88 // list of all reference paths successfully read
89 const std::vector<std::string> &ref_path_list() const
90 {
91 return ref_succeed_list_;
92 }
93
94 // merge status as a string
95 const char *status_string() const
96 {
97 switch (status_)
98 {
99 case MERGE_UNDEFINED:
100 return "MERGE_UNDEFINED";
101 case MERGE_SUCCESS:
102 return "MERGE_SUCCESS";
103 case MERGE_EXCEPTION:
104 return "MERGE_EXCEPTION";
106 return "MERGE_OVPN_EXT_FAIL";
108 return "MERGE_OVPN_FILE_FAIL";
109 case MERGE_REF_FAIL:
110 return "MERGE_REF_FAIL";
112 return "MERGE_MULTIPLE_REF_FAIL";
113 default:
114 return "MERGE_?";
115 }
116 }
117
118 // allow following of external file references
125
126 ProfileMerge(const std::string &profile_path,
127 const std::string &profile_ext,
128 const std::string &profile_dir_override,
129 const Follow follow_references,
130 const size_t max_line_len,
131 const size_t max_size)
133 {
134 try
135 {
136 size_t total_size = 0;
137
138 // read the profile
139 std::string orig_profile_content;
140 std::string profile_dir;
141 try
142 {
143 profile_dir = !profile_dir_override.empty() ? profile_dir_override : path::dirname(profile_path);
144 basename_ = path::basename(profile_path);
145 const std::string ext = path::ext(basename_);
146 if (profile_ext.empty() || string::strcasecmp(ext, profile_ext) == 0)
147 {
148 orig_profile_content = read_text_utf8(profile_path, max_size);
149 total_size = orig_profile_content.size();
150 }
151 else
152 {
154 error_ = std::string("ERR_PROFILE_NO_OVPN_EXTENSION: ") + basename_;
155 return;
156 }
157 }
158 catch (const file_is_binary &e)
159 {
161 error_ = std::string("ERR_PROFILE_FILE_IS_BINARY: ") + e.what();
162 return;
163 }
164 catch (const file_too_large &e)
165 {
167 error_ = std::string("ERR_PROFILE_FILE_TOO_LARGE: ") + e.what();
168 return;
169 }
170 catch (const std::exception &e)
171 {
173 error_ = std::string("ERR_PROFILE_GENERIC: ") + e.what();
174 return;
175 }
176
177 // expand the profile
178 expand_profile(orig_profile_content, profile_dir, follow_references, max_line_len, max_size, total_size);
179 }
180 catch (const std::exception &e)
181 {
183 error_ = std::string("ERR_PROFILE_GENERIC: ") + e.what();
184 }
185 }
186
187 static std::string merge(const std::string &profile_path,
188 const std::string &profile_ext,
189 const std::string &profile_dir_override,
190 const Follow follow_references,
191 const size_t max_line_len,
192 const size_t max_size)
193 {
194 const ProfileMerge pm(profile_path,
195 profile_ext,
196 profile_dir_override,
197 follow_references,
198 max_line_len,
199 max_size);
201 return pm.profile_content();
202 OPENVPN_THROW(merge_error, pm.status_string() << ": " << pm.error());
203 }
204
205 protected:
210
211 void expand_profile(const std::string &orig_profile_content,
212 const std::string &profile_dir,
213 const Follow follow_references,
214 const size_t max_line_len,
215 const size_t max_size,
216 size_t total_size)
217 {
218 if (total_size > max_size)
219 {
221 error_ = "ERR_PROFILE_FILE_TOO_LARGE: file too large";
222 return;
223 }
224
226
227 SplitLines in(orig_profile_content, max_line_len);
228 bool in_multiline = false;
229 bool opaque_multiline = false;
230 Option multiline;
231
232 profile_content_.reserve(orig_profile_content.length());
233 while (in(true))
234 {
235 if (in.line_overflow())
236 {
238 error_ = "ERR_PROFILE_LINE_TOO_LONG: line too long";
239 return;
240 }
241 const std::string &line = in.line_ref();
242 bool echo = true;
243 if (in_multiline)
244 {
245 if (OptionList::is_close_tag(line, multiline.ref(0)))
246 {
247 multiline.clear();
248 in_multiline = false;
249 opaque_multiline = false;
250 }
251 }
252 else if (!OptionList::ignore_line(line))
253 {
254 Option opt = Split::by_space<Option, OptionList::LexComment, SpaceMatch, Split::NullLimit>(line);
255 if (!opt.empty())
256 {
257 if (OptionList::is_open_tag(opt.ref(0)) && opt.size() == 1)
258 {
260 multiline = std::move(opt);
261 in_multiline = true;
262 unsigned int flags = 0; // not used
263 opaque_multiline = is_fileref_directive(multiline.ref(0), flags);
264 }
265 else
266 {
267 unsigned int flags = 0;
268 bool is_fileref = (!opaque_multiline
269 && opt.size() >= 2
270 && is_fileref_directive(opt.ref(0), flags));
271 if (is_fileref)
272 {
273 // check if http-proxy directive references a creds file
274 if (flags & F_HTTP_PROXY)
275 {
276 is_fileref = false;
277 if (opt.size() >= 4)
278 {
279 const std::string authfile = opt.get(3, 256);
280 if (authfile != "auto" && authfile != "auto-nct"
281 && authfile != "basic" && authfile != "digest"
282 && authfile != "ntlm" && authfile != "none")
283 {
284 opt.ref(3) = "auto";
285 profile_content_ += opt.escape(false);
286 profile_content_ += '\n';
287 opt.ref(0) = "http-proxy-user-pass";
288 opt.ref(1) = authfile;
289 opt.resize(2);
290 is_fileref = true;
291 }
292 }
293 }
294 }
295 if (is_fileref)
296 {
297 // found a directive referencing a file
298
299 // get basename of file and make sure that it doesn't
300 // attempt to traverse directories (unless
301 // follow_references == FOLLOW_FULL)
302 const std::string fn_str = opt.get(1, 256);
303 const std::string fn = (follow_references == FOLLOW_FULL ? fn_str : path::basename(fn_str));
304 if (fn.empty())
305 {
306 echo = false;
308 error_ = "ERR_PROFILE_NO_FILENAME: filename not provided";
309 }
310 else if (follow_references != FOLLOW_FULL && !path::is_flat(fn))
311 {
312 echo = false;
314 error_ = std::string("ERR_PROFILE_CANT_FOLLOW_LINK: ") + fn;
315 if (ref_fail_list_.size() < MAX_FN_LIST_SIZE)
316 ref_fail_list_.push_back(fn);
317 }
318 else
319 {
320 std::string path;
321 std::string file_content;
322 bool error = false;
323 try
324 {
325 if (follow_references == FOLLOW_NONE)
326 {
328 error_ = std::string("ERR_PROFILE_CANT_FOLLOW_LINK: ") + fn + ": cannot follow file reference";
329 return;
330 }
331 path = path::join(profile_dir, fn);
332 file_content = read_text_utf8(path, max_size);
333 total_size += file_content.size();
334 if (total_size > max_size)
335 {
337 error_ = std::string("ERR_PROFILE_FILE_TOO_LARGE: ") + fn + ": file too large";
338 return;
339 }
340 OptionList::detect_multiline_breakout(file_content, opt.ref(0));
341 }
342 catch (const std::exception &e)
343 {
344 error = true;
346 error_ = std::string("ERR_PROFILE_GENERIC: ") + fn + " : " + e.what();
347 if (ref_fail_list_.size() < MAX_FN_LIST_SIZE)
348 ref_fail_list_.push_back(fn);
349 }
350
351 if (!error) // succeeded in reading file?
352 {
353 // don't echo this line, i.e. opt[], instead expand file_content into profile
354 echo = false;
355
356 // tls-auth or secret directive may include key-direction parameter
358 {
359 std::string key_direction;
360 if (opt.size() >= 3)
361 key_direction = opt.get(2, 16);
362 else
363 key_direction = "bidirectional";
364 profile_content_ += "key-direction " + key_direction + "\n";
365 }
366
367 // format file_content for appending to profile
368 {
369 std::ostringstream os;
370 const std::string &tag = opt.ref(0);
371 string::add_trailing(file_content, '\n');
372 os << '<' << tag << ">\n"
373 << file_content << "</" << tag << ">\n";
374 profile_content_ += os.str();
375 }
376
377 // save file we referenced
379 ref_succeed_list_.push_back(std::move(path));
380 }
381 }
382 }
383 }
384 }
385 }
386 if (echo)
387 {
388 profile_content_ += line;
389 profile_content_ += '\n';
390 }
391 }
392
393 // If more than 2 errors occurred, change status to
394 // MERGE_MULTIPLE_REF_FAIL and enumerate each failed file.
395 if (ref_fail_list_.size() >= 2)
396 {
398 error_ = "ERR_PROFILE_GENERIC: ";
399 for (size_t i = 0; i < ref_fail_list_.size(); ++i)
400 {
401 if (i)
402 error_ += ", ";
404 }
405 }
406 }
407
408 static bool is_fileref_directive(const std::string &d, unsigned int &flags)
409 {
410 if (!d.empty())
411 {
412 switch (d[0])
413 {
414 case 'a':
415 return d == "auth-user-pass";
416 case 'c':
417 return d == "ca" || d == "cert" || d == "crl-verify";
418 case 'd':
419 return d == "dh";
420 case 'e':
421 return d == "extra-certs";
422 case 'h':
423 if (d == "http-proxy")
424 {
426 return true;
427 }
428 return false;
429 case 'k':
430 return d == "key";
431#if 0 // define when we have capability to parse out pkcs12 from profile and add to Keychain (fixme)
432 case 'p':
433 if (d == "pkcs12")
434 {
435 flags |= F_PKCS12;
436 return true;
437 }
438 return false;
439#endif
440 case 'r':
441 if (d == "relay-extra-ca")
442 return true;
443 if (d == "relay-tls-auth")
444 {
446 return true;
447 }
448 return false;
449 case 's':
450 if (d == "static-key")
451 return true;
452 return false;
453 case 't':
454 if (d == "tls-auth")
455 {
457 return true;
458 }
459 if (d == "tls-crypt")
460 return true;
461 if (d == "tls-crypt-v2")
462 return true;
463 return false;
464 }
465 }
466 return false;
467 }
468
470 std::string profile_content_;
471 std::string basename_;
472 std::string error_;
473 std::vector<std::string> ref_fail_list_;
474 std::vector<std::string> ref_succeed_list_;
475};
476
478{
479 public:
481 const std::string &ref_dir,
482 const Follow follow_references,
483 const size_t max_line_len,
484 const size_t max_size)
485 {
486 try
487 {
488 // expand the profile
490 ref_dir,
491 follow_references,
492 max_line_len,
493 max_size,
494 profile_content.size());
495 }
496 catch (const std::exception &e)
497 {
499 error_ = std::string("ERR_PROFILE_GENERIC: ") + e.what();
500 }
501 }
502
503 static std::string merge(const std::string &profile_content,
504 const std::string &ref_dir,
505 const Follow follow_references,
506 const size_t max_line_len,
507 const size_t max_size)
508 {
510 ref_dir,
511 follow_references,
512 max_line_len,
513 max_size);
515 return pm.profile_content();
516 OPENVPN_THROW(merge_error, pm.status_string() << ": " << pm.error());
517 }
518};
519} // namespace openvpn
520
521#endif
static void detect_multiline_breakout(const std::string &opt, const std::string &tag)
Definition options.hpp:1621
static void untag_open_tag(std::string &str)
Definition options.hpp:1588
static bool is_close_tag(const std::string &str, const std::string &tag)
Definition options.hpp:1581
static bool ignore_line(const std::string &line)
Definition options.hpp:1560
static bool is_open_tag(const std::string &str)
Definition options.hpp:1574
const std::string & get(const size_t index, const size_t max_len) const
Definition options.hpp:184
size_t size() const
Definition options.hpp:320
void resize(const size_t n)
Definition options.hpp:340
void reserve(const size_t n)
Definition options.hpp:336
bool empty() const
Definition options.hpp:324
std::string escape(const bool csv) const
Definition options.hpp:295
const std::string & ref(const size_t i) const
Definition options.hpp:346
ProfileMergeFromString(const std::string &profile_content, const std::string &ref_dir, const Follow follow_references, const size_t max_line_len, const size_t max_size)
Definition merge.hpp:480
static std::string merge(const std::string &profile_content, const std::string &ref_dir, const Follow follow_references, const size_t max_line_len, const size_t max_size)
Definition merge.hpp:503
void expand_profile(const std::string &orig_profile_content, const std::string &profile_dir, const Follow follow_references, const size_t max_line_len, const size_t max_size, size_t total_size)
Definition merge.hpp:211
const std::string & basename() const
Definition merge.hpp:77
const std::string & error() const
Definition merge.hpp:71
OPENVPN_EXCEPTION(merge_error)
std::string error_
Definition merge.hpp:472
const char * status_string() const
Definition merge.hpp:95
std::string basename_
Definition merge.hpp:471
ProfileMerge(const std::string &profile_path, const std::string &profile_ext, const std::string &profile_dir_override, const Follow follow_references, const size_t max_line_len, const size_t max_size)
Definition merge.hpp:126
static bool is_fileref_directive(const std::string &d, unsigned int &flags)
Definition merge.hpp:408
const std::string & profile_content() const
Definition merge.hpp:83
std::vector< std::string > ref_fail_list_
Definition merge.hpp:473
const std::vector< std::string > & ref_path_list() const
Definition merge.hpp:89
std::vector< std::string > ref_succeed_list_
Definition merge.hpp:474
Status status() const
Definition merge.hpp:67
std::string profile_content_
Definition merge.hpp:470
static std::string merge(const std::string &profile_path, const std::string &profile_ext, const std::string &profile_dir_override, const Follow follow_references, const size_t max_line_len, const size_t max_size)
Definition merge.hpp:187
bool line_overflow() const
std::string & line_ref()
#define OPENVPN_THROW(exc, stuff)
std::string ext(const std::string &basename)
Definition path.hpp:152
std::string dirname(const std::string &path)
Definition path.hpp:89
std::string basename(const std::string &path)
Definition path.hpp:76
std::string join(const std::string &p1, const std::string &p2)
Definition path.hpp:173
bool is_flat(const std::string &path)
Definition path.hpp:67
int strcasecmp(const char *s1, const char *s2)
Definition string.hpp:33
void add_trailing(std::string &str, const char c)
Definition string.hpp:134
std::string read_text_utf8(const std::string &filename, const std::uint64_t max_size=0)
Definition file.hpp:136
reroute_gw flags
std::ostringstream os
void ext(const std::string &path)
Definition test_path.cpp:24