Browse code

dns: support running up/down command with privsep

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>

Heiko Hund authored on 2025/05/17 17:38:27
Showing 10 changed files
... ...
@@ -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