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 else
203 OPENVPN_THROW(merge_error, pm.status_string() << ": " << pm.error());
204 }
205
206 protected:
211
212 void expand_profile(const std::string &orig_profile_content,
213 const std::string &profile_dir,
214 const Follow follow_references,
215 const size_t max_line_len,
216 const size_t max_size,
217 size_t total_size)
218 {
219 if (total_size > max_size)
220 {
222 error_ = "ERR_PROFILE_FILE_TOO_LARGE: file too large";
223 return;
224 }
225
227
228 SplitLines in(orig_profile_content, max_line_len);
229 bool in_multiline = false;
230 bool opaque_multiline = false;
231 Option multiline;
232
233 profile_content_.reserve(orig_profile_content.length());
234 while (in(true))
235 {
236 if (in.line_overflow())
237 {
239 error_ = "ERR_PROFILE_LINE_TOO_LONG: line too long";
240 return;
241 }
242 const std::string &line = in.line_ref();
243 bool echo = true;
244 if (in_multiline)
245 {
246 if (OptionList::is_close_tag(line, multiline.ref(0)))
247 {
248 multiline.clear();
249 in_multiline = false;
250 opaque_multiline = false;
251 }
252 }
253 else if (!OptionList::ignore_line(line))
254 {
255 Option opt = Split::by_space<Option, OptionList::LexComment, SpaceMatch, Split::NullLimit>(line);
256 if (opt.size())
257 {
258 if (OptionList::is_open_tag(opt.ref(0)) && opt.size() == 1)
259 {
261 multiline = std::move(opt);
262 in_multiline = true;
263 unsigned int flags = 0; // not used
264 opaque_multiline = is_fileref_directive(multiline.ref(0), flags);
265 }
266 else
267 {
268 unsigned int flags = 0;
269 bool is_fileref = (!opaque_multiline
270 && opt.size() >= 2
271 && is_fileref_directive(opt.ref(0), flags));
272 if (is_fileref)
273 {
274 // check if http-proxy directive references a creds file
275 if (flags & F_HTTP_PROXY)
276 {
277 is_fileref = false;
278 if (opt.size() >= 4)
279 {
280 const std::string authfile = opt.get(3, 256);
281 if (authfile != "auto" && authfile != "auto-nct"
282 && authfile != "basic" && authfile != "digest"
283 && authfile != "ntlm" && authfile != "none")
284 {
285 opt.ref(3) = "auto";
286 profile_content_ += opt.escape(false);
287 profile_content_ += '\n';
288 opt.ref(0) = "http-proxy-user-pass";
289 opt.ref(1) = authfile;
290 opt.resize(2);
291 is_fileref = true;
292 }
293 }
294 }
295 }
296 if (is_fileref)
297 {
298 // found a directive referencing a file
299
300 // get basename of file and make sure that it doesn't
301 // attempt to traverse directories (unless
302 // follow_references == FOLLOW_FULL)
303 const std::string fn_str = opt.get(1, 256);
304 const std::string fn = (follow_references == FOLLOW_FULL ? fn_str : path::basename(fn_str));
305 if (fn.empty())
306 {
307 echo = false;
309 error_ = "ERR_PROFILE_NO_FILENAME: filename not provided";
310 }
311 else if (follow_references != FOLLOW_FULL && !path::is_flat(fn))
312 {
313 echo = false;
315 error_ = std::string("ERR_PROFILE_CANT_FOLLOW_LINK: ") + fn;
316 if (ref_fail_list_.size() < MAX_FN_LIST_SIZE)
317 ref_fail_list_.push_back(fn);
318 }
319 else
320 {
321 std::string path;
322 std::string file_content;
323 bool error = false;
324 try
325 {
326 if (follow_references == FOLLOW_NONE)
327 {
329 error_ = std::string("ERR_PROFILE_CANT_FOLLOW_LINK: ") + fn + ": cannot follow file reference";
330 return;
331 }
332 path = path::join(profile_dir, fn);
333 file_content = read_text_utf8(path, max_size);
334 total_size += file_content.size();
335 if (total_size > max_size)
336 {
338 error_ = std::string("ERR_PROFILE_FILE_TOO_LARGE: ") + fn + ": file too large";
339 return;
340 }
341 OptionList::detect_multiline_breakout(file_content, opt.ref(0));
342 }
343 catch (const std::exception &e)
344 {
345 error = true;
347 error_ = std::string("ERR_PROFILE_GENERIC: ") + fn + " : " + e.what();
348 if (ref_fail_list_.size() < MAX_FN_LIST_SIZE)
349 ref_fail_list_.push_back(fn);
350 }
351
352 if (!error) // succeeded in reading file?
353 {
354 // don't echo this line, i.e. opt[], instead expand file_content into profile
355 echo = false;
356
357 // tls-auth or secret directive may include key-direction parameter
359 {
360 std::string key_direction;
361 if (opt.size() >= 3)
362 key_direction = opt.get(2, 16);
363 else
364 key_direction = "bidirectional";
365 profile_content_ += "key-direction " + key_direction + "\n";
366 }
367
368 // format file_content for appending to profile
369 {
370 std::ostringstream os;
371 const std::string &tag = opt.ref(0);
372 string::add_trailing(file_content, '\n');
373 os << '<' << tag << ">\n"
374 << file_content << "</" << tag << ">\n";
375 profile_content_ += os.str();
376 }
377
378 // save file we referenced
380 ref_succeed_list_.push_back(std::move(path));
381 }
382 }
383 }
384 }
385 }
386 }
387 if (echo)
388 {
389 profile_content_ += line;
390 profile_content_ += '\n';
391 }
392 }
393
394 // If more than 2 errors occurred, change status to
395 // MERGE_MULTIPLE_REF_FAIL and enumerate each failed file.
396 if (ref_fail_list_.size() >= 2)
397 {
399 error_ = "ERR_PROFILE_GENERIC: ";
400 for (size_t i = 0; i < ref_fail_list_.size(); ++i)
401 {
402 if (i)
403 error_ += ", ";
405 }
406 }
407 }
408
409 static bool is_fileref_directive(const std::string &d, unsigned int &flags)
410 {
411 if (d.length() > 0)
412 {
413 switch (d[0])
414 {
415 case 'a':
416 return d == "auth-user-pass";
417 case 'c':
418 return d == "ca" || d == "cert" || d == "crl-verify";
419 case 'd':
420 return d == "dh";
421 case 'e':
422 return d == "extra-certs";
423 case 'h':
424 if (d == "http-proxy")
425 {
427 return true;
428 }
429 return false;
430 case 'k':
431 return d == "key";
432#if 0 // define when we have capability to parse out pkcs12 from profile and add to Keychain (fixme)
433 case 'p':
434 if (d == "pkcs12")
435 {
436 flags |= F_PKCS12;
437 return true;
438 }
439 return false;
440#endif
441 case 'r':
442 if (d == "relay-extra-ca")
443 return true;
444 if (d == "relay-tls-auth")
445 {
447 return true;
448 }
449 return false;
450 case 's':
451 if (d == "static-key")
452 return true;
453 return false;
454 case 't':
455 if (d == "tls-auth")
456 {
458 return true;
459 }
460 if (d == "tls-crypt")
461 return true;
462 if (d == "tls-crypt-v2")
463 return true;
464 return false;
465 }
466 }
467 return false;
468 }
469
471 std::string profile_content_;
472 std::string basename_;
473 std::string error_;
474 std::vector<std::string> ref_fail_list_;
475 std::vector<std::string> ref_succeed_list_;
476};
477
479{
480 public:
482 const std::string &ref_dir,
483 const Follow follow_references,
484 const size_t max_line_len,
485 const size_t max_size)
486 {
487 try
488 {
489 // expand the profile
491 ref_dir,
492 follow_references,
493 max_line_len,
494 max_size,
495 profile_content.size());
496 }
497 catch (const std::exception &e)
498 {
500 error_ = std::string("ERR_PROFILE_GENERIC: ") + e.what();
501 }
502 }
503
504 static std::string merge(const std::string &profile_content,
505 const std::string &ref_dir,
506 const Follow follow_references,
507 const size_t max_line_len,
508 const size_t max_size)
509 {
511 ref_dir,
512 follow_references,
513 max_line_len,
514 max_size);
516 return pm.profile_content();
517 else
518 OPENVPN_THROW(merge_error, pm.status_string() << ": " << pm.error());
519 }
520};
521} // namespace openvpn
522
523#endif
static void detect_multiline_breakout(const std::string &opt, const std::string &tag)
Definition options.hpp:1642
static void untag_open_tag(std::string &str)
Definition options.hpp:1609
static bool is_close_tag(const std::string &str, const std::string &tag)
Definition options.hpp:1602
static bool ignore_line(const std::string &line)
Definition options.hpp:1581
static bool is_open_tag(const std::string &str)
Definition options.hpp:1595
const std::string & get(const size_t index, const size_t max_len) const
Definition options.hpp:187
size_t size() const
Definition options.hpp:327
void resize(const size_t n)
Definition options.hpp:347
void reserve(const size_t n)
Definition options.hpp:343
std::string escape(const bool csv) const
Definition options.hpp:302
const std::string & ref(const size_t i) const
Definition options.hpp:353
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:481
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:504
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:212
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:473
const char * status_string() const
Definition merge.hpp:95
std::string basename_
Definition merge.hpp:472
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:409
const std::string & profile_content() const
Definition merge.hpp:83
std::vector< std::string > ref_fail_list_
Definition merge.hpp:474
const std::vector< std::string > & ref_path_list() const
Definition merge.hpp:89
std::vector< std::string > ref_succeed_list_
Definition merge.hpp:475
Status status() const
Definition merge.hpp:67
std::string profile_content_
Definition merge.hpp:471
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:156
std::string dirname(const std::string &path)
Definition path.hpp:91
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:180
bool is_flat(const std::string &path)
Definition path.hpp:67
int strcasecmp(const char *s1, const char *s2)
Definition string.hpp:29
void add_trailing(std::string &str, const char c)
Definition string.hpp:184
Support deferred server-side state creation when client connects.
Definition ovpncli.cpp:95
std::string read_text_utf8(const std::string &filename, const std::uint64_t max_size=0)
Definition file.hpp:136
reroute_gw flags
void ext(const std::string &path)
Definition test_path.cpp:24