diff --git a/libopenconnect.map.in b/libopenconnect.map.in index bf52a24b..6412b198 100644 --- a/libopenconnect.map.in +++ b/libopenconnect.map.in @@ -39,6 +39,9 @@ OPENCONNECT_3.1 { global: openconnect_setup_cmd_pipe; openconnect_mainloop; + openconnect_setup_tun_device; + openconnect_setup_tun_script; + openconnect_setup_tun_fd; } OPENCONNECT_3.0; OPENCONNECT_PRIVATE { diff --git a/library.c b/library.c index 5a980fd2..373e71ea 100644 --- a/library.c +++ b/library.c @@ -123,6 +123,8 @@ void openconnect_vpninfo_free(struct openconnect_info *vpninfo) free(vpninfo->redirect_url); free(vpninfo->proxy_type); free(vpninfo->proxy); + free(vpninfo->vpnc_script); + free(vpninfo->ifname); if (vpninfo->csd_scriptname) { unlink(vpninfo->csd_scriptname); diff --git a/main.c b/main.c index 353d0f53..67825f3a 100644 --- a/main.c +++ b/main.c @@ -508,6 +508,8 @@ int main(int argc, char **argv) int use_syslog = 0; char *urlpath = NULL; char *proxy = getenv("https_proxy"); + int script_tun = 0; + char *vpnc_script = NULL, *ifname = NULL; int autoproxy = 0; uid_t uid = getuid(); int opt; @@ -554,7 +556,6 @@ int main(int argc, char **argv) vpninfo->process_auth_form = process_auth_form_cb; vpninfo->cbdata = vpninfo; vpninfo->cert_expire_warning = 60 * 86400; - vpninfo->vpnc_script = DEFAULT_VPNCSCRIPT; vpninfo->cmd_fd = -1; vpninfo->xmlpost = 1; @@ -659,7 +660,7 @@ int main(int argc, char **argv) case 'h': usage(); case 'i': - vpninfo->ifname = keep_config_arg(); + ifname = xstrdup(config_arg); break; case 'l': use_syslog = 1; @@ -703,10 +704,10 @@ int main(int argc, char **argv) nocertcheck = 1; break; case 's': - vpninfo->vpnc_script = keep_config_arg(); + vpnc_script = xstrdup(config_arg); break; case 'S': - vpninfo->script_tun = 1; + script_tun = 1; break; case 'u': free(username); @@ -922,7 +923,14 @@ int main(int argc, char **argv) exit(1); } - if (setup_tun(vpninfo)) { + if (!vpnc_script) + vpnc_script = xstrdup(DEFAULT_VPNCSCRIPT); + if (script_tun) { + if (openconnect_setup_tun_script(vpninfo, vpnc_script)) { + fprintf(stderr, _("Set up tun script failed\n")); + exit(1); + } + } else if (openconnect_setup_tun_device(vpninfo, vpnc_script, ifname)) { fprintf(stderr, _("Set up tun device failed\n")); exit(1); } diff --git a/openconnect-internal.h b/openconnect-internal.h index 574894e1..903ea398 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -263,7 +263,7 @@ struct openconnect_info { unsigned char dtls_secret[48]; char *dtls_cipher; - const char *vpnc_script; + char *vpnc_script; int script_tun; char *ifname; @@ -371,7 +371,6 @@ char *openconnect__strcasestr(const char *haystack, const char *needle); /****************************************************************************/ /* tun.c */ -int setup_tun(struct openconnect_info *vpninfo); int tun_mainloop(struct openconnect_info *vpninfo, int *timeout); void shutdown_tun(struct openconnect_info *vpninfo); int script_config_tun(struct openconnect_info *vpninfo, const char *reason); diff --git a/openconnect.h b/openconnect.h index a30ca056..1ecc198a 100644 --- a/openconnect.h +++ b/openconnect.h @@ -261,6 +261,16 @@ int openconnect_setup_cmd_pipe(struct openconnect_info *vpninfo); const char *openconnect_get_version(void); +/* Create a tun device through the OS kernel (typical use case). Both + strings are optional and can be NULL if desired. */ +int openconnect_setup_tun_device(struct openconnect_info *vpninfo, char *vpnc_script, char *ifname); + +/* Pass traffic to a script program (no tun device). */ +int openconnect_setup_tun_script(struct openconnect_info *vpninfo, char *tun_script); + +/* Caller will provide a file descriptor for the tunnel traffic. */ +int openconnect_setup_tun_fd(struct openconnect_info *vpninfo, int tun_fd); + /* Start the main loop; exits if OC_CMD_CANCEL is received on cmd_fd or the remote site aborts. */ int openconnect_mainloop(struct openconnect_info *vpninfo, diff --git a/tun.c b/tun.c index 0db7c439..94b718bb 100644 --- a/tun.c +++ b/tun.c @@ -631,64 +631,78 @@ static int os_setup_tun(struct openconnect_info *vpninfo) return tun_fd; } -/* Set up a tuntap device. */ -int setup_tun(struct openconnect_info *vpninfo) +int openconnect_setup_tun_fd(struct openconnect_info *vpninfo, int tun_fd) { - int tun_fd; + fcntl(tun_fd, F_SETFD, FD_CLOEXEC); - set_script_env(vpninfo); + if (vpninfo->tun_fd != -1) + FD_CLR(vpninfo->tun_fd, &vpninfo->select_rfds); + vpninfo->tun_fd = tun_fd; - if (vpninfo->script_tun) { - pid_t child; - int fds[2]; + if (vpninfo->select_nfds <= tun_fd) + vpninfo->select_nfds = tun_fd + 1; - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fds)) { - perror(_("socketpair")); - exit(1); - } - tun_fd = fds[0]; - child = fork(); - if (child < 0) { - perror(_("fork")); - exit(1); - } else if (!child) { - if (setpgid(0, getpid()) < 0) - perror(_("setpgid")); - close(tun_fd); - setenv_int("VPNFD", fds[1]); - execl("/bin/sh", "/bin/sh", "-c", vpninfo->vpnc_script, NULL); - perror(_("execl")); - exit(1); - } - close(fds[1]); - vpninfo->script_tun = child; - vpninfo->ifname = strdup(_("(script)")); - } else { - script_config_tun(vpninfo, "pre-init"); + FD_SET(tun_fd, &vpninfo->select_rfds); - tun_fd = os_setup_tun(vpninfo); - if (tun_fd < 0) - return tun_fd; + fcntl(vpninfo->tun_fd, F_SETFL, fcntl(vpninfo->tun_fd, F_GETFL) | O_NONBLOCK); - setenv("TUNDEV", vpninfo->ifname, 1); - script_config_tun(vpninfo, "connect"); + return 0; +} + +int openconnect_setup_tun_script(struct openconnect_info *vpninfo, char *tun_script) +{ + pid_t child; + int fds[2]; - /* Ancient vpnc-scripts might not get this right */ - set_tun_mtu(vpninfo); + vpninfo->vpnc_script = tun_script; + vpninfo->script_tun = 1; + + set_script_env(vpninfo); + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fds)) { + perror(_("socketpair")); + exit(1); + } + child = fork(); + if (child < 0) { + perror(_("fork")); + exit(1); + } else if (!child) { + if (setpgid(0, getpid()) < 0) + perror(_("setpgid")); + close(fds[0]); + setenv_int("VPNFD", fds[1]); + execl("/bin/sh", "/bin/sh", "-c", vpninfo->vpnc_script, NULL); + perror(_("execl")); + exit(1); } + close(fds[1]); + vpninfo->script_tun = child; + vpninfo->ifname = strdup(_("(script)")); - fcntl(tun_fd, F_SETFD, FD_CLOEXEC); + return openconnect_setup_tun_fd(vpninfo, fds[0]); +} - vpninfo->tun_fd = tun_fd; +int openconnect_setup_tun_device(struct openconnect_info *vpninfo, char *vpnc_script, char *ifname) +{ + int tun_fd; - if (vpninfo->select_nfds <= tun_fd) - vpninfo->select_nfds = tun_fd + 1; + vpninfo->vpnc_script = vpnc_script; + vpninfo->ifname = ifname; - FD_SET(tun_fd, &vpninfo->select_rfds); + set_script_env(vpninfo); + script_config_tun(vpninfo, "pre-init"); - fcntl(vpninfo->tun_fd, F_SETFL, fcntl(vpninfo->tun_fd, F_GETFL) | O_NONBLOCK); + tun_fd = os_setup_tun(vpninfo); + if (tun_fd < 0) + return tun_fd; - return 0; + setenv("TUNDEV", vpninfo->ifname, 1); + script_config_tun(vpninfo, "connect"); + + /* Ancient vpnc-scripts might not get this right */ + set_tun_mtu(vpninfo); + + return openconnect_setup_tun_fd(vpninfo, tun_fd); } static struct pkt *out_pkt; @@ -802,6 +816,7 @@ void shutdown_tun(struct openconnect_info *vpninfo) #endif } - close(vpninfo->tun_fd); + if (vpninfo->vpnc_script) + close(vpninfo->tun_fd); vpninfo->tun_fd = -1; }