With --user privileges are dropped after init. Unfortunately this
affects --dns-updown when undoing previous modifications.
To keep the privileges for just that, the concept of a dns updown runner
in introduced. It's basically a fork of openvpn at the time the
modifications to DNS are made. Its only capability is running the
--dns-updown command when asked to. The parent openvpn process signals
this by writing to a pipe the runner is waiting on.
Commands need to be ready to receive variables from a file instead of the
process environment. A shameless and effective workaround to keep the
protocol between the two processes simple.
Change-Id: I6b67e3a00dd84bf348b6af28115ee11138c3a111
Signed-off-by: Heiko Hund <heiko@ist.eigentlich.net>
Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <20250517083833.28728-1-gert@greenie.muc.de>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg31668.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>
| ... | ... |
@@ -7,6 +7,10 @@ |
| 7 | 7 |
# |
| 8 | 8 |
# Example env from openvpn (most are not applied): |
| 9 | 9 |
# |
| 10 |
+# dns_vars_file /tmp/openvpn_dvf_58b95c0c97b2db43afb5d745f986c53c.tmp |
|
| 11 |
+# |
|
| 12 |
+# or |
|
| 13 |
+# |
|
| 10 | 14 |
# dev tun0 |
| 11 | 15 |
# script_type dns-up |
| 12 | 16 |
# dns_search_domain_1 mycorp.in |
| ... | ... |
@@ -39,6 +43,7 @@ conly_standard_server_ports() {
|
| 39 | 39 |
|
| 40 | 40 |
conf=/boot/system/settings/network/resolv.conf |
| 41 | 41 |
test -e "$conf" || exit 1 |
| 42 |
+test -z "${dns_vars_file}" || . "${dns_vars_file}"
|
|
| 42 | 43 |
case "${script_type}" in
|
| 43 | 44 |
dns-up) |
| 44 | 45 |
n=1 |
| ... | ... |
@@ -8,6 +8,10 @@ |
| 8 | 8 |
# |
| 9 | 9 |
# Example env from openvpn (most are not applied): |
| 10 | 10 |
# |
| 11 |
+# dns_vars_file /tmp/openvpn_dvf_58b95c0c97b2db43afb5d745f986c53c.tmp |
|
| 12 |
+# |
|
| 13 |
+# or |
|
| 14 |
+# |
|
| 11 | 15 |
# dev tun0 |
| 12 | 16 |
# script_type dns-up |
| 13 | 17 |
# dns_search_domain_1 mycorp.in |
| ... | ... |
@@ -38,6 +42,7 @@ only_standard_server_ports() {
|
| 38 | 38 |
done |
| 39 | 39 |
} |
| 40 | 40 |
|
| 41 |
+[ -z "${dns_vars_file}" ] || . "${dns_vars_file}"
|
|
| 41 | 42 |
: ${script_type:=dns-down}
|
| 42 | 43 |
case "${script_type}" in
|
| 43 | 44 |
dns-up) |
| ... | ... |
@@ -7,6 +7,10 @@ |
| 7 | 7 |
# |
| 8 | 8 |
# Example env from openvpn (most are not applied): |
| 9 | 9 |
# |
| 10 |
+# dns_vars_file /tmp/openvpn_dvf_58b95c0c97b2db43afb5d745f986c53c.tmp |
|
| 11 |
+# |
|
| 12 |
+# or |
|
| 13 |
+# |
|
| 10 | 14 |
# dev tun0 |
| 11 | 15 |
# script_type dns-up |
| 12 | 16 |
# dns_search_domain_1 mycorp.in |
| ... | ... |
@@ -39,6 +43,7 @@ only_standard_server_ports() {
|
| 39 | 39 |
|
| 40 | 40 |
conf=/etc/resolv.conf |
| 41 | 41 |
test -e "$conf" || exit 1 |
| 42 |
+test -z "${dns_vars_file}" || . "${dns_vars_file}"
|
|
| 42 | 43 |
case "${script_type}" in
|
| 43 | 44 |
dns-up) |
| 44 | 45 |
n=1 |
| ... | ... |
@@ -15,6 +15,10 @@ |
| 15 | 15 |
# |
| 16 | 16 |
# Example env from openvpn (not all are always applied): |
| 17 | 17 |
# |
| 18 |
+# dns_vars_file /tmp/openvpn_dvf_58b95c0c97b2db43afb5d745f986c53c.tmp |
|
| 19 |
+# |
|
| 20 |
+# or |
|
| 21 |
+# |
|
| 18 | 22 |
# dev tun0 |
| 19 | 23 |
# script_type dns-up |
| 20 | 24 |
# dns_search_domain_1 mycorp.in |
| ... | ... |
@@ -30,6 +34,8 @@ |
| 30 | 30 |
# dns_server_1_sni dns.mycorp.in |
| 31 | 31 |
# |
| 32 | 32 |
|
| 33 |
+[ -z "${dns_vars_file}" ] || . "${dns_vars_file}"
|
|
| 34 |
+ |
|
| 33 | 35 |
function do_resolved_servers {
|
| 34 | 36 |
local sni="" |
| 35 | 37 |
local transport_var=dns_server_${n}_transport
|
| ... | ... |
@@ -562,13 +562,20 @@ updown_env_set(bool up, const struct dns_options *o, const struct tuntap *tt, st |
| 562 | 562 |
} |
| 563 | 563 |
|
| 564 | 564 |
static int |
| 565 |
-do_run_up_down_command(bool up, const struct dns_options *o, const struct tuntap *tt) |
|
| 565 |
+do_run_up_down_command(bool up, const char *vars_file, const struct dns_options *o, const struct tuntap *tt) |
|
| 566 | 566 |
{
|
| 567 | 567 |
struct gc_arena gc = gc_new(); |
| 568 | 568 |
struct argv argv = argv_new(); |
| 569 | 569 |
struct env_set *es = env_set_create(&gc); |
| 570 | 570 |
|
| 571 |
- updown_env_set(up, o, tt, es); |
|
| 571 |
+ if (vars_file) |
|
| 572 |
+ {
|
|
| 573 |
+ setenv_str(es, "dns_vars_file", vars_file); |
|
| 574 |
+ } |
|
| 575 |
+ else |
|
| 576 |
+ {
|
|
| 577 |
+ updown_env_set(up, o, tt, es); |
|
| 578 |
+ } |
|
| 572 | 579 |
|
| 573 | 580 |
argv_printf(&argv, "%s", o->updown); |
| 574 | 581 |
argv_msg(M_INFO, &argv); |
| ... | ... |
@@ -586,8 +593,115 @@ do_run_up_down_command(bool up, const struct dns_options *o, const struct tuntap |
| 586 | 586 |
return res; |
| 587 | 587 |
} |
| 588 | 588 |
|
| 589 |
+static bool |
|
| 590 |
+run_updown_runner(bool up, struct options *o, const struct tuntap *tt, struct dns_updown_runner_info *updown_runner) |
|
| 591 |
+{
|
|
| 592 |
+ int dns_pipe_fd[2]; |
|
| 593 |
+ int ack_pipe_fd[2]; |
|
| 594 |
+ if (pipe(dns_pipe_fd) != 0 |
|
| 595 |
+ || pipe(ack_pipe_fd) != 0) |
|
| 596 |
+ {
|
|
| 597 |
+ msg(M_ERR | M_ERRNO, "run_dns_up_down: unable to create pipes"); |
|
| 598 |
+ return false; |
|
| 599 |
+ } |
|
| 600 |
+ updown_runner->pid = fork(); |
|
| 601 |
+ if (updown_runner->pid == -1) |
|
| 602 |
+ {
|
|
| 603 |
+ msg(M_ERR | M_ERRNO, "run_dns_up_down: unable to fork"); |
|
| 604 |
+ close(dns_pipe_fd[0]); |
|
| 605 |
+ close(dns_pipe_fd[1]); |
|
| 606 |
+ close(ack_pipe_fd[0]); |
|
| 607 |
+ close(ack_pipe_fd[1]); |
|
| 608 |
+ return false; |
|
| 609 |
+ } |
|
| 610 |
+ else if (updown_runner->pid > 0) |
|
| 611 |
+ {
|
|
| 612 |
+ /* Parent process */ |
|
| 613 |
+ close(dns_pipe_fd[0]); |
|
| 614 |
+ close(ack_pipe_fd[1]); |
|
| 615 |
+ updown_runner->fds[0] = ack_pipe_fd[0]; |
|
| 616 |
+ updown_runner->fds[1] = dns_pipe_fd[1]; |
|
| 617 |
+ } |
|
| 618 |
+ else |
|
| 619 |
+ {
|
|
| 620 |
+ /* Script runner process, close unused FDs */ |
|
| 621 |
+ for (int fd = 3; fd < 100; ++fd) |
|
| 622 |
+ {
|
|
| 623 |
+ if (fd != dns_pipe_fd[0] |
|
| 624 |
+ && fd != ack_pipe_fd[1]) |
|
| 625 |
+ {
|
|
| 626 |
+ close(fd); |
|
| 627 |
+ } |
|
| 628 |
+ } |
|
| 629 |
+ |
|
| 630 |
+ /* Ignore signals */ |
|
| 631 |
+ signal(SIGINT, SIG_IGN); |
|
| 632 |
+ signal(SIGHUP, SIG_IGN); |
|
| 633 |
+ signal(SIGTERM, SIG_IGN); |
|
| 634 |
+ signal(SIGUSR1, SIG_IGN); |
|
| 635 |
+ signal(SIGUSR2, SIG_IGN); |
|
| 636 |
+ signal(SIGPIPE, SIG_IGN); |
|
| 637 |
+ |
|
| 638 |
+ while (1) |
|
| 639 |
+ {
|
|
| 640 |
+ ssize_t rlen, wlen; |
|
| 641 |
+ char path[PATH_MAX]; |
|
| 642 |
+ |
|
| 643 |
+ /* Block here until parent sends a path */ |
|
| 644 |
+ rlen = read(dns_pipe_fd[0], &path, sizeof(path)); |
|
| 645 |
+ if (rlen < 1) |
|
| 646 |
+ {
|
|
| 647 |
+ if (rlen == -1 && errno == EINTR) |
|
| 648 |
+ {
|
|
| 649 |
+ continue; |
|
| 650 |
+ } |
|
| 651 |
+ close(dns_pipe_fd[0]); |
|
| 652 |
+ close(ack_pipe_fd[1]); |
|
| 653 |
+ exit(0); |
|
| 654 |
+ } |
|
| 655 |
+ |
|
| 656 |
+ path[sizeof(path) - 1] = '\0'; |
|
| 657 |
+ int res = do_run_up_down_command(up, path, &o->dns_options, tt); |
|
| 658 |
+ platform_unlink(path); |
|
| 659 |
+ |
|
| 660 |
+ /* Unblock parent process */ |
|
| 661 |
+ while (1) |
|
| 662 |
+ {
|
|
| 663 |
+ wlen = write(ack_pipe_fd[1], &res, sizeof(res)); |
|
| 664 |
+ if ((wlen == -1 && errno != EINTR) || wlen < sizeof(res)) |
|
| 665 |
+ {
|
|
| 666 |
+ /* Not much we can do about errors but exit */ |
|
| 667 |
+ close(dns_pipe_fd[0]); |
|
| 668 |
+ close(ack_pipe_fd[1]); |
|
| 669 |
+ exit(0); |
|
| 670 |
+ } |
|
| 671 |
+ else if (wlen == sizeof(res)) |
|
| 672 |
+ {
|
|
| 673 |
+ break; |
|
| 674 |
+ } |
|
| 675 |
+ } |
|
| 676 |
+ |
|
| 677 |
+ up = !up; /* do the opposite next time */ |
|
| 678 |
+ } |
|
| 679 |
+ } |
|
| 680 |
+ |
|
| 681 |
+ return true; |
|
| 682 |
+} |
|
| 683 |
+ |
|
| 684 |
+static const char * |
|
| 685 |
+write_dns_vars_file(bool up, const struct options *o, const struct tuntap *tt, struct gc_arena *gc) |
|
| 686 |
+{
|
|
| 687 |
+ struct env_set *es = env_set_create(gc); |
|
| 688 |
+ const char *dvf = platform_create_temp_file(o->tmp_dir, "dvf", gc); |
|
| 689 |
+ |
|
| 690 |
+ updown_env_set(up, &o->dns_options, tt, es); |
|
| 691 |
+ env_set_write_file(dvf, es); |
|
| 692 |
+ |
|
| 693 |
+ return dvf; |
|
| 694 |
+} |
|
| 695 |
+ |
|
| 589 | 696 |
static void |
| 590 |
-run_up_down_command(bool up, struct options *o, const struct tuntap *tt) |
|
| 697 |
+run_up_down_command(bool up, struct options *o, const struct tuntap *tt, struct dns_updown_runner_info *updown_runner) |
|
| 591 | 698 |
{
|
| 592 | 699 |
if (!o->dns_options.updown) |
| 593 | 700 |
{
|
| ... | ... |
@@ -595,7 +709,60 @@ run_up_down_command(bool up, struct options *o, const struct tuntap *tt) |
| 595 | 595 |
} |
| 596 | 596 |
|
| 597 | 597 |
int status; |
| 598 |
- status = do_run_up_down_command(up, &o->dns_options, tt); |
|
| 598 |
+ |
|
| 599 |
+ if (!updown_runner->required) |
|
| 600 |
+ {
|
|
| 601 |
+ /* Run dns updown directly */ |
|
| 602 |
+ status = do_run_up_down_command(up, NULL, &o->dns_options, tt); |
|
| 603 |
+ } |
|
| 604 |
+ else |
|
| 605 |
+ {
|
|
| 606 |
+ if (updown_runner->pid < 1) |
|
| 607 |
+ {
|
|
| 608 |
+ /* Need to set up privilege preserving child first */ |
|
| 609 |
+ if (!run_updown_runner(up, o, tt, updown_runner)) |
|
| 610 |
+ {
|
|
| 611 |
+ return; |
|
| 612 |
+ } |
|
| 613 |
+ } |
|
| 614 |
+ |
|
| 615 |
+ struct gc_arena gc = gc_new(); |
|
| 616 |
+ int rfd = updown_runner->fds[0]; |
|
| 617 |
+ int wfd = updown_runner->fds[1]; |
|
| 618 |
+ const char *dvf = write_dns_vars_file(up, o, tt, &gc); |
|
| 619 |
+ size_t dvf_size = strlen(dvf) + 1; |
|
| 620 |
+ |
|
| 621 |
+ while (1) |
|
| 622 |
+ {
|
|
| 623 |
+ ssize_t len = write(wfd, dvf, dvf_size); |
|
| 624 |
+ if (len < dvf_size) |
|
| 625 |
+ {
|
|
| 626 |
+ if (len == -1 && errno == EINTR) |
|
| 627 |
+ {
|
|
| 628 |
+ continue; |
|
| 629 |
+ } |
|
| 630 |
+ msg(M_ERR | M_ERRNO, "could not send dns vars filename"); |
|
| 631 |
+ } |
|
| 632 |
+ break; |
|
| 633 |
+ } |
|
| 634 |
+ |
|
| 635 |
+ while (1) |
|
| 636 |
+ {
|
|
| 637 |
+ ssize_t len = read(rfd, &status, sizeof(status)); |
|
| 638 |
+ if (len < sizeof(status)) |
|
| 639 |
+ {
|
|
| 640 |
+ if (len == -1 && errno == EINTR) |
|
| 641 |
+ {
|
|
| 642 |
+ continue; |
|
| 643 |
+ } |
|
| 644 |
+ msg(M_ERR | M_ERRNO, "could not receive dns updown status"); |
|
| 645 |
+ } |
|
| 646 |
+ break; |
|
| 647 |
+ } |
|
| 648 |
+ |
|
| 649 |
+ gc_free(&gc); |
|
| 650 |
+ } |
|
| 651 |
+ |
|
| 599 | 652 |
msg(M_INFO, "dns %s command exited with status %d", up ? "up" : "down", status); |
| 600 | 653 |
} |
| 601 | 654 |
|
| ... | ... |
@@ -681,7 +848,7 @@ show_dns_options(const struct dns_options *o) |
| 681 | 681 |
} |
| 682 | 682 |
|
| 683 | 683 |
void |
| 684 |
-run_dns_up_down(bool up, struct options *o, const struct tuntap *tt) |
|
| 684 |
+run_dns_up_down(bool up, struct options *o, const struct tuntap *tt, struct dns_updown_runner_info *duri) |
|
| 685 | 685 |
{
|
| 686 | 686 |
if (!o->dns_options.servers) |
| 687 | 687 |
{
|
| ... | ... |
@@ -718,6 +885,6 @@ run_dns_up_down(bool up, struct options *o, const struct tuntap *tt) |
| 718 | 718 |
#ifdef _WIN32 |
| 719 | 719 |
run_up_down_service(up, o, tt); |
| 720 | 720 |
#else |
| 721 |
- run_up_down_command(up, o, tt); |
|
| 721 |
+ run_up_down_command(up, o, tt, duri); |
|
| 722 | 722 |
#endif /* ifdef _WIN32 */ |
| 723 | 723 |
} |
| ... | ... |
@@ -68,6 +68,14 @@ struct dns_server {
|
| 68 | 68 |
const char *sni; |
| 69 | 69 |
}; |
| 70 | 70 |
|
| 71 |
+struct dns_updown_runner_info {
|
|
| 72 |
+ bool required; |
|
| 73 |
+ int fds[2]; |
|
| 74 |
+#if !defined(_WIN32) |
|
| 75 |
+ pid_t pid; |
|
| 76 |
+#endif |
|
| 77 |
+}; |
|
| 78 |
+ |
|
| 71 | 79 |
struct dns_options {
|
| 72 | 80 |
struct dns_domain *search_domains; |
| 73 | 81 |
struct dns_server *servers_prepull; |
| ... | ... |
@@ -154,8 +162,10 @@ void dns_options_postprocess_pull(struct dns_options *o); |
| 154 | 154 |
* @param up Boolean to set this call to "up" when true |
| 155 | 155 |
* @param o Pointer to the program options |
| 156 | 156 |
* @param tt Pointer to the connection's tuntap struct |
| 157 |
+ * @param duri Pointer to the updown runner info struct |
|
| 157 | 158 |
*/ |
| 158 |
-void run_dns_up_down(bool up, struct options *o, const struct tuntap *tt); |
|
| 159 |
+void run_dns_up_down(bool up, struct options *o, const struct tuntap *tt, |
|
| 160 |
+ struct dns_updown_runner_info *duri); |
|
| 159 | 161 |
|
| 160 | 162 |
/** |
| 161 | 163 |
* Puts the DNS options into an environment set. |
| ... | ... |
@@ -33,6 +33,7 @@ |
| 33 | 33 |
#include "env_set.h" |
| 34 | 34 |
|
| 35 | 35 |
#include "run_command.h" |
| 36 |
+#include "platform.h" |
|
| 36 | 37 |
|
| 37 | 38 |
/* |
| 38 | 39 |
* Set environmental variable (int or string). |
| ... | ... |
@@ -235,6 +236,30 @@ env_set_print(int msglevel, const struct env_set *es) |
| 235 | 235 |
} |
| 236 | 236 |
|
| 237 | 237 |
void |
| 238 |
+env_set_write_file(const char *path, const struct env_set *es) |
|
| 239 |
+{
|
|
| 240 |
+ FILE *fp = platform_fopen(path, "w"); |
|
| 241 |
+ if (!fp) |
|
| 242 |
+ {
|
|
| 243 |
+ msg(M_ERR, "could not write env set to '%s'", path); |
|
| 244 |
+ return; |
|
| 245 |
+ } |
|
| 246 |
+ |
|
| 247 |
+ if (es) |
|
| 248 |
+ {
|
|
| 249 |
+ const struct env_item *item = es->list; |
|
| 250 |
+ while (item) |
|
| 251 |
+ {
|
|
| 252 |
+ fputs(item->string, fp); |
|
| 253 |
+ fputc('\n', fp);
|
|
| 254 |
+ item = item->next; |
|
| 255 |
+ } |
|
| 256 |
+ } |
|
| 257 |
+ |
|
| 258 |
+ fclose(fp); |
|
| 259 |
+} |
|
| 260 |
+ |
|
| 261 |
+void |
|
| 238 | 262 |
env_set_inherit(struct env_set *es, const struct env_set *src) |
| 239 | 263 |
{
|
| 240 | 264 |
const struct env_item *e; |
| ... | ... |
@@ -91,6 +91,14 @@ const char *env_set_get(const struct env_set *es, const char *name); |
| 91 | 91 |
|
| 92 | 92 |
void env_set_print(int msglevel, const struct env_set *es); |
| 93 | 93 |
|
| 94 |
+/** |
|
| 95 |
+ * Write a struct env_set to a file. Each item on one line. |
|
| 96 |
+ * |
|
| 97 |
+ * @param path The filepath to write to. |
|
| 98 |
+ * @param es Pointer to the env_set to write. |
|
| 99 |
+ */ |
|
| 100 |
+void env_set_write_file(const char *path, const struct env_set *es); |
|
| 101 |
+ |
|
| 94 | 102 |
void env_set_inherit(struct env_set *es, const struct env_set *src); |
| 95 | 103 |
|
| 96 | 104 |
/* returns true if environmental variable name starts with 'password' */ |
| ... | ... |
@@ -2027,7 +2027,7 @@ do_open_tun(struct context *c, int *error_flags) |
| 2027 | 2027 |
c->c2.frame.tun_mtu, c->c2.es, &c->net_ctx); |
| 2028 | 2028 |
} |
| 2029 | 2029 |
|
| 2030 |
- run_dns_up_down(true, &c->options, c->c1.tuntap); |
|
| 2030 |
+ run_dns_up_down(true, &c->options, c->c1.tuntap, &c->persist.duri); |
|
| 2031 | 2031 |
|
| 2032 | 2032 |
/* run the up script */ |
| 2033 | 2033 |
run_up_down(c->options.up_script, |
| ... | ... |
@@ -2067,7 +2067,7 @@ do_open_tun(struct context *c, int *error_flags) |
| 2067 | 2067 |
/* explicitly set the ifconfig_* env vars */ |
| 2068 | 2068 |
do_ifconfig_setenv(c->c1.tuntap, c->c2.es); |
| 2069 | 2069 |
|
| 2070 |
- run_dns_up_down(true, &c->options, c->c1.tuntap); |
|
| 2070 |
+ run_dns_up_down(true, &c->options, c->c1.tuntap, &c->persist.duri); |
|
| 2071 | 2071 |
|
| 2072 | 2072 |
/* run the up script if user specified --up-restart */ |
| 2073 | 2073 |
if (c->options.up_restart) |
| ... | ... |
@@ -2157,7 +2157,7 @@ do_close_tun(struct context *c, bool force) |
| 2157 | 2157 |
adapter_index = c->c1.tuntap->adapter_index; |
| 2158 | 2158 |
#endif |
| 2159 | 2159 |
|
| 2160 |
- run_dns_up_down(false, &c->options, c->c1.tuntap); |
|
| 2160 |
+ run_dns_up_down(false, &c->options, c->c1.tuntap, &c->persist.duri); |
|
| 2161 | 2161 |
|
| 2162 | 2162 |
if (force || !(c->sig->signal_received == SIGUSR1 && c->options.persist_tun)) |
| 2163 | 2163 |
{
|
| ... | ... |
@@ -3971,6 +3971,9 @@ do_init_first_time(struct context *c) |
| 3971 | 3971 |
|
| 3972 | 3972 |
c0->uid_gid_specified = user_defined || group_defined; |
| 3973 | 3973 |
|
| 3974 |
+ /* fork the dns script runner to preserve root? */ |
|
| 3975 |
+ c->persist.duri.required = user_defined; |
|
| 3976 |
+ |
|
| 3974 | 3977 |
/* perform postponed chdir if --daemon */ |
| 3975 | 3978 |
if (c->did_we_daemonize && c->options.cd_dir == NULL) |
| 3976 | 3979 |
{
|
| ... | ... |
@@ -45,6 +45,7 @@ |
| 45 | 45 |
#include "pool.h" |
| 46 | 46 |
#include "plugin.h" |
| 47 | 47 |
#include "manage.h" |
| 48 |
+#include "dns.h" |
|
| 48 | 49 |
|
| 49 | 50 |
/* |
| 50 | 51 |
* Our global key schedules, packaged thusly |
| ... | ... |
@@ -120,6 +121,7 @@ struct context_buffers |
| 120 | 120 |
struct context_persist |
| 121 | 121 |
{
|
| 122 | 122 |
int restart_sleep_seconds; |
| 123 |
+ struct dns_updown_runner_info duri; |
|
| 123 | 124 |
}; |
| 124 | 125 |
|
| 125 | 126 |
|