diff options
author | Alberto Gonzalez Iniesta <agi@inittab.org> | 2016-11-21 09:37:33 +0100 |
---|---|---|
committer | Alberto Gonzalez Iniesta <agi@inittab.org> | 2016-11-21 09:37:33 +0100 |
commit | 93b77cacdbb7e6f310c4e20f85c3a24ed5ba18ba (patch) | |
tree | 55a7688c9969ef4d01625caa58c7f679098c76eb /src/openvpnserv | |
parent | daa9ef0efeb5e10a1b43820fbab3a4ff5fbd22f1 (diff) | |
parent | 20c8675ba46bda97330a4117c459a59a9f1c465e (diff) |
Merge tag 'upstream/2.4_beta1'
Upstream version 2.4~beta1
Diffstat (limited to 'src/openvpnserv')
-rw-r--r-- | src/openvpnserv/Makefile.am | 16 | ||||
-rw-r--r-- | src/openvpnserv/Makefile.in | 181 | ||||
-rw-r--r-- | src/openvpnserv/automatic.c | 415 | ||||
-rw-r--r-- | src/openvpnserv/common.c | 218 | ||||
-rw-r--r-- | src/openvpnserv/interactive.c | 1652 | ||||
-rwxr-xr-x | src/openvpnserv/openvpnserv.c | 534 | ||||
-rw-r--r-- | src/openvpnserv/openvpnserv.vcxproj | 2 | ||||
-rw-r--r-- | src/openvpnserv/service.c | 861 | ||||
-rw-r--r-- | src/openvpnserv/service.h | 215 | ||||
-rw-r--r-- | src/openvpnserv/validate.c | 249 | ||||
-rw-r--r-- | src/openvpnserv/validate.h | 48 |
11 files changed, 3028 insertions, 1363 deletions
diff --git a/src/openvpnserv/Makefile.am b/src/openvpnserv/Makefile.am index a989c25..3521a34 100644 --- a/src/openvpnserv/Makefile.am +++ b/src/openvpnserv/Makefile.am @@ -17,11 +17,23 @@ EXTRA_DIST = \ openvpnserv.vcxproj \ openvpnserv.vcxproj.filters +AM_CPPFLAGS = \ + -I$(top_srcdir)/include -I$(top_srcdir)/src/openvpn -I$(top_srcdir)/src/compat + if WIN32 sbin_PROGRAMS = openvpnserv +openvpnserv_CFLAGS = \ + -municode -D_UNICODE \ + -UNTDDI_VERSION -U_WIN32_WINNT \ + -D_WIN32_WINNT=_WIN32_WINNT_VISTA +openvpnserv_LDADD = -ladvapi32 -luserenv -liphlpapi -lfwpuclnt -lrpcrt4 -lshlwapi -lnetapi32 -lws2_32 endif openvpnserv_SOURCES = \ - openvpnserv.c \ - service.h service.c \ + common.c \ + automatic.c \ + interactive.c \ + service.c service.h \ + validate.c validate.h \ + $(top_srcdir)/src/openvpn/block_dns.c $(top_srcdir)/src/openvpn/block_dns.h \ openvpnserv_resources.rc diff --git a/src/openvpnserv/Makefile.in b/src/openvpnserv/Makefile.in index ed1ee90..74a802b 100644 --- a/src/openvpnserv/Makefile.in +++ b/src/openvpnserv/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.15 from Makefile.am. +# Makefile.in generated by automake 1.13.4 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2014 Free Software Foundation, Inc. +# Copyright (C) 1994-2013 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -37,17 +37,7 @@ # Required to build Windows resource file VPATH = @srcdir@ -am__is_gnu_make = { \ - if test -z '$(MAKELEVEL)'; then \ - false; \ - elif test -n '$(MAKE_HOST)'; then \ - true; \ - elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ - true; \ - else \ - false; \ - fi; \ -} +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ @@ -110,6 +100,8 @@ PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ +DIST_COMMON = $(top_srcdir)/build/ltrc.inc $(srcdir)/Makefile.in \ + $(srcdir)/Makefile.am $(top_srcdir)/depcomp @WIN32_TRUE@sbin_PROGRAMS = openvpnserv$(EXEEXT) subdir = src/openvpnserv ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 @@ -122,21 +114,28 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_emptyarray.m4 \ $(top_srcdir)/compat.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) -DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d -CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_HEADER = $(top_builddir)/config.h \ + $(top_builddir)/include/openvpn-plugin.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(sbindir)" PROGRAMS = $(sbin_PROGRAMS) -am_openvpnserv_OBJECTS = openvpnserv.$(OBJEXT) service.$(OBJEXT) \ +am_openvpnserv_OBJECTS = openvpnserv-common.$(OBJEXT) \ + openvpnserv-automatic.$(OBJEXT) \ + openvpnserv-interactive.$(OBJEXT) \ + openvpnserv-service.$(OBJEXT) openvpnserv-validate.$(OBJEXT) \ + openvpnserv-block_dns.$(OBJEXT) \ openvpnserv_resources.$(OBJEXT) openvpnserv_OBJECTS = $(am_openvpnserv_OBJECTS) -openvpnserv_LDADD = $(LDADD) +openvpnserv_DEPENDENCIES = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = +openvpnserv_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(openvpnserv_CFLAGS) \ + $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false @@ -149,7 +148,7 @@ AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = -DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) -I$(top_builddir)/include depcomp = $(SHELL) $(top_srcdir)/depcomp am__depfiles_maybe = depfiles am__mv = mv -f @@ -197,8 +196,6 @@ am__define_uniq_tagged_files = \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags -am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/build/ltrc.inc \ - $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ @@ -212,6 +209,7 @@ AWK = @AWK@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CFLAGS = @CFLAGS@ +CMAKE = @CMAKE@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CYGPATH_W = @CYGPATH_W@ @@ -246,25 +244,31 @@ LIBTOOL = @LIBTOOL@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBOBJS = @LTLIBOBJS@ -LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LZ4_CFLAGS = @LZ4_CFLAGS@ +LZ4_LIBS = @LZ4_LIBS@ LZO_CFLAGS = @LZO_CFLAGS@ LZO_LIBS = @LZO_LIBS@ MAKEINFO = @MAKEINFO@ MAN2HTML = @MAN2HTML@ MANIFEST_TOOL = @MANIFEST_TOOL@ +MBEDTLS_CFLAGS = @MBEDTLS_CFLAGS@ +MBEDTLS_LIBS = @MBEDTLS_LIBS@ MKDIR_P = @MKDIR_P@ NETSTAT = @NETSTAT@ NM = @NM@ NMEDIT = @NMEDIT@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ -OPENSSL_CRYPTO_CFLAGS = @OPENSSL_CRYPTO_CFLAGS@ -OPENSSL_CRYPTO_LIBS = @OPENSSL_CRYPTO_LIBS@ -OPENSSL_SSL_CFLAGS = @OPENSSL_SSL_CFLAGS@ -OPENSSL_SSL_LIBS = @OPENSSL_SSL_LIBS@ +OPENSSL_CFLAGS = @OPENSSL_CFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OPENVPN_VERSION_MAJOR = @OPENVPN_VERSION_MAJOR@ +OPENVPN_VERSION_MINOR = @OPENVPN_VERSION_MINOR@ +OPENVPN_VERSION_PATCH = @OPENVPN_VERSION_PATCH@ OPTIONAL_CRYPTO_CFLAGS = @OPTIONAL_CRYPTO_CFLAGS@ OPTIONAL_CRYPTO_LIBS = @OPTIONAL_CRYPTO_LIBS@ OPTIONAL_DL_LIBS = @OPTIONAL_DL_LIBS@ +OPTIONAL_LZ4_CFLAGS = @OPTIONAL_LZ4_CFLAGS@ +OPTIONAL_LZ4_LIBS = @OPTIONAL_LZ4_LIBS@ OPTIONAL_LZO_CFLAGS = @OPTIONAL_LZO_CFLAGS@ OPTIONAL_LZO_LIBS = @OPTIONAL_LZO_LIBS@ OPTIONAL_PKCS11_HELPER_CFLAGS = @OPTIONAL_PKCS11_HELPER_CFLAGS@ @@ -290,8 +294,6 @@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ PLUGIN_AUTH_PAM_CFLAGS = @PLUGIN_AUTH_PAM_CFLAGS@ PLUGIN_AUTH_PAM_LIBS = @PLUGIN_AUTH_PAM_LIBS@ -POLARSSL_CFLAGS = @POLARSSL_CFLAGS@ -POLARSSL_LIBS = @POLARSSL_LIBS@ RANLIB = @RANLIB@ RC = @RC@ ROUTE = @ROUTE@ @@ -306,6 +308,11 @@ TAP_CFLAGS = @TAP_CFLAGS@ TAP_WIN_COMPONENT_ID = @TAP_WIN_COMPONENT_ID@ TAP_WIN_MIN_MAJOR = @TAP_WIN_MIN_MAJOR@ TAP_WIN_MIN_MINOR = @TAP_WIN_MIN_MINOR@ +TEST_CFLAGS = @TEST_CFLAGS@ +TEST_LDFLAGS = @TEST_LDFLAGS@ +VENDOR_BUILD_ROOT = @VENDOR_BUILD_ROOT@ +VENDOR_DIST_ROOT = @VENDOR_DIST_ROOT@ +VENDOR_SRC_ROOT = @VENDOR_SRC_ROOT@ VERSION = @VERSION@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ @@ -372,9 +379,22 @@ EXTRA_DIST = \ openvpnserv.vcxproj \ openvpnserv.vcxproj.filters +AM_CPPFLAGS = \ + -I$(top_srcdir)/include -I$(top_srcdir)/src/openvpn -I$(top_srcdir)/src/compat + +@WIN32_TRUE@openvpnserv_CFLAGS = \ +@WIN32_TRUE@ -municode -D_UNICODE \ +@WIN32_TRUE@ -UNTDDI_VERSION -U_WIN32_WINNT \ +@WIN32_TRUE@ -D_WIN32_WINNT=_WIN32_WINNT_VISTA + +@WIN32_TRUE@openvpnserv_LDADD = -ladvapi32 -luserenv -liphlpapi -lfwpuclnt -lrpcrt4 -lshlwapi -lnetapi32 -lws2_32 openvpnserv_SOURCES = \ - openvpnserv.c \ - service.h service.c \ + common.c \ + automatic.c \ + interactive.c \ + service.c service.h \ + validate.c validate.h \ + $(top_srcdir)/src/openvpn/block_dns.c $(top_srcdir)/src/openvpn/block_dns.h \ openvpnserv_resources.rc all: all-am @@ -393,6 +413,7 @@ $(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(top_srcdir)/build/ltrc.inc $(am_ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/openvpnserv/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/openvpnserv/Makefile +.PRECIOUS: Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ @@ -401,7 +422,7 @@ Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ esac; -$(top_srcdir)/build/ltrc.inc $(am__empty): +$(top_srcdir)/build/ltrc.inc: $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh @@ -463,7 +484,7 @@ clean-sbinPROGRAMS: openvpnserv$(EXEEXT): $(openvpnserv_OBJECTS) $(openvpnserv_DEPENDENCIES) $(EXTRA_openvpnserv_DEPENDENCIES) @rm -f openvpnserv$(EXEEXT) - $(AM_V_CCLD)$(LINK) $(openvpnserv_OBJECTS) $(openvpnserv_LDADD) $(LIBS) + $(AM_V_CCLD)$(openvpnserv_LINK) $(openvpnserv_OBJECTS) $(openvpnserv_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) @@ -471,22 +492,26 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/openvpnserv.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/service.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/openvpnserv-automatic.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/openvpnserv-block_dns.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/openvpnserv-common.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/openvpnserv-interactive.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/openvpnserv-service.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/openvpnserv-validate.Po@am__quote@ .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @@ -495,6 +520,90 @@ distclean-compile: @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< +openvpnserv-common.o: common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -MT openvpnserv-common.o -MD -MP -MF $(DEPDIR)/openvpnserv-common.Tpo -c -o openvpnserv-common.o `test -f 'common.c' || echo '$(srcdir)/'`common.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openvpnserv-common.Tpo $(DEPDIR)/openvpnserv-common.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='common.c' object='openvpnserv-common.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -c -o openvpnserv-common.o `test -f 'common.c' || echo '$(srcdir)/'`common.c + +openvpnserv-common.obj: common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -MT openvpnserv-common.obj -MD -MP -MF $(DEPDIR)/openvpnserv-common.Tpo -c -o openvpnserv-common.obj `if test -f 'common.c'; then $(CYGPATH_W) 'common.c'; else $(CYGPATH_W) '$(srcdir)/common.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openvpnserv-common.Tpo $(DEPDIR)/openvpnserv-common.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='common.c' object='openvpnserv-common.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -c -o openvpnserv-common.obj `if test -f 'common.c'; then $(CYGPATH_W) 'common.c'; else $(CYGPATH_W) '$(srcdir)/common.c'; fi` + +openvpnserv-automatic.o: automatic.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -MT openvpnserv-automatic.o -MD -MP -MF $(DEPDIR)/openvpnserv-automatic.Tpo -c -o openvpnserv-automatic.o `test -f 'automatic.c' || echo '$(srcdir)/'`automatic.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openvpnserv-automatic.Tpo $(DEPDIR)/openvpnserv-automatic.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='automatic.c' object='openvpnserv-automatic.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -c -o openvpnserv-automatic.o `test -f 'automatic.c' || echo '$(srcdir)/'`automatic.c + +openvpnserv-automatic.obj: automatic.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -MT openvpnserv-automatic.obj -MD -MP -MF $(DEPDIR)/openvpnserv-automatic.Tpo -c -o openvpnserv-automatic.obj `if test -f 'automatic.c'; then $(CYGPATH_W) 'automatic.c'; else $(CYGPATH_W) '$(srcdir)/automatic.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openvpnserv-automatic.Tpo $(DEPDIR)/openvpnserv-automatic.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='automatic.c' object='openvpnserv-automatic.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -c -o openvpnserv-automatic.obj `if test -f 'automatic.c'; then $(CYGPATH_W) 'automatic.c'; else $(CYGPATH_W) '$(srcdir)/automatic.c'; fi` + +openvpnserv-interactive.o: interactive.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -MT openvpnserv-interactive.o -MD -MP -MF $(DEPDIR)/openvpnserv-interactive.Tpo -c -o openvpnserv-interactive.o `test -f 'interactive.c' || echo '$(srcdir)/'`interactive.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openvpnserv-interactive.Tpo $(DEPDIR)/openvpnserv-interactive.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='interactive.c' object='openvpnserv-interactive.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -c -o openvpnserv-interactive.o `test -f 'interactive.c' || echo '$(srcdir)/'`interactive.c + +openvpnserv-interactive.obj: interactive.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -MT openvpnserv-interactive.obj -MD -MP -MF $(DEPDIR)/openvpnserv-interactive.Tpo -c -o openvpnserv-interactive.obj `if test -f 'interactive.c'; then $(CYGPATH_W) 'interactive.c'; else $(CYGPATH_W) '$(srcdir)/interactive.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openvpnserv-interactive.Tpo $(DEPDIR)/openvpnserv-interactive.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='interactive.c' object='openvpnserv-interactive.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -c -o openvpnserv-interactive.obj `if test -f 'interactive.c'; then $(CYGPATH_W) 'interactive.c'; else $(CYGPATH_W) '$(srcdir)/interactive.c'; fi` + +openvpnserv-service.o: service.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -MT openvpnserv-service.o -MD -MP -MF $(DEPDIR)/openvpnserv-service.Tpo -c -o openvpnserv-service.o `test -f 'service.c' || echo '$(srcdir)/'`service.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openvpnserv-service.Tpo $(DEPDIR)/openvpnserv-service.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='service.c' object='openvpnserv-service.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -c -o openvpnserv-service.o `test -f 'service.c' || echo '$(srcdir)/'`service.c + +openvpnserv-service.obj: service.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -MT openvpnserv-service.obj -MD -MP -MF $(DEPDIR)/openvpnserv-service.Tpo -c -o openvpnserv-service.obj `if test -f 'service.c'; then $(CYGPATH_W) 'service.c'; else $(CYGPATH_W) '$(srcdir)/service.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openvpnserv-service.Tpo $(DEPDIR)/openvpnserv-service.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='service.c' object='openvpnserv-service.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -c -o openvpnserv-service.obj `if test -f 'service.c'; then $(CYGPATH_W) 'service.c'; else $(CYGPATH_W) '$(srcdir)/service.c'; fi` + +openvpnserv-validate.o: validate.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -MT openvpnserv-validate.o -MD -MP -MF $(DEPDIR)/openvpnserv-validate.Tpo -c -o openvpnserv-validate.o `test -f 'validate.c' || echo '$(srcdir)/'`validate.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openvpnserv-validate.Tpo $(DEPDIR)/openvpnserv-validate.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='validate.c' object='openvpnserv-validate.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -c -o openvpnserv-validate.o `test -f 'validate.c' || echo '$(srcdir)/'`validate.c + +openvpnserv-validate.obj: validate.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -MT openvpnserv-validate.obj -MD -MP -MF $(DEPDIR)/openvpnserv-validate.Tpo -c -o openvpnserv-validate.obj `if test -f 'validate.c'; then $(CYGPATH_W) 'validate.c'; else $(CYGPATH_W) '$(srcdir)/validate.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openvpnserv-validate.Tpo $(DEPDIR)/openvpnserv-validate.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='validate.c' object='openvpnserv-validate.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -c -o openvpnserv-validate.obj `if test -f 'validate.c'; then $(CYGPATH_W) 'validate.c'; else $(CYGPATH_W) '$(srcdir)/validate.c'; fi` + +openvpnserv-block_dns.o: $(top_srcdir)/src/openvpn/block_dns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -MT openvpnserv-block_dns.o -MD -MP -MF $(DEPDIR)/openvpnserv-block_dns.Tpo -c -o openvpnserv-block_dns.o `test -f '$(top_srcdir)/src/openvpn/block_dns.c' || echo '$(srcdir)/'`$(top_srcdir)/src/openvpn/block_dns.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openvpnserv-block_dns.Tpo $(DEPDIR)/openvpnserv-block_dns.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(top_srcdir)/src/openvpn/block_dns.c' object='openvpnserv-block_dns.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -c -o openvpnserv-block_dns.o `test -f '$(top_srcdir)/src/openvpn/block_dns.c' || echo '$(srcdir)/'`$(top_srcdir)/src/openvpn/block_dns.c + +openvpnserv-block_dns.obj: $(top_srcdir)/src/openvpn/block_dns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -MT openvpnserv-block_dns.obj -MD -MP -MF $(DEPDIR)/openvpnserv-block_dns.Tpo -c -o openvpnserv-block_dns.obj `if test -f '$(top_srcdir)/src/openvpn/block_dns.c'; then $(CYGPATH_W) '$(top_srcdir)/src/openvpn/block_dns.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/openvpn/block_dns.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openvpnserv-block_dns.Tpo $(DEPDIR)/openvpnserv-block_dns.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(top_srcdir)/src/openvpn/block_dns.c' object='openvpnserv-block_dns.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(openvpnserv_CFLAGS) $(CFLAGS) -c -o openvpnserv-block_dns.obj `if test -f '$(top_srcdir)/src/openvpn/block_dns.c'; then $(CYGPATH_W) '$(top_srcdir)/src/openvpn/block_dns.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/openvpn/block_dns.c'; fi` + mostlyclean-libtool: -rm -f *.lo @@ -708,8 +817,6 @@ uninstall-am: uninstall-sbinPROGRAMS mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ tags tags-am uninstall uninstall-am uninstall-sbinPROGRAMS -.PRECIOUS: Makefile - .rc.lo: $(LTRCCOMPILE) -i "$<" -o "$@" diff --git a/src/openvpnserv/automatic.c b/src/openvpnserv/automatic.c new file mode 100644 index 0000000..aa7618f --- /dev/null +++ b/src/openvpnserv/automatic.c @@ -0,0 +1,415 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * This program allows one or more OpenVPN processes to be started + * as a service. To build, you must get the service sample from the + * Platform SDK and replace Simple.c with this file. + * + * You should also apply service.patch to + * service.c and service.h from the Platform SDK service sample. + * + * This code is designed to be built with the mingw compiler. + */ + +#include "service.h" + +#include <stdio.h> +#include <stdarg.h> +#include <process.h> + +/* bool definitions */ +#define bool int +#define true 1 +#define false 0 + +static SERVICE_STATUS_HANDLE service; +static SERVICE_STATUS status; + +openvpn_service_t automatic_service = { + automatic, + TEXT(PACKAGE_NAME "ServiceLegacy"), + TEXT(PACKAGE_NAME " Legacy Service"), + TEXT(SERVICE_DEPENDENCIES), + SERVICE_DEMAND_START +}; + +struct security_attributes +{ + SECURITY_ATTRIBUTES sa; + SECURITY_DESCRIPTOR sd; +}; + +/* + * Which registry key in HKLM should + * we get config info from? + */ +#define REG_KEY "SOFTWARE\\" PACKAGE_NAME + +static HANDLE exit_event = NULL; + +/* clear an object */ +#define CLEAR(x) memset(&(x), 0, sizeof(x)) + + +bool +init_security_attributes_allow_all (struct security_attributes *obj) +{ + CLEAR (*obj); + + obj->sa.nLength = sizeof (SECURITY_ATTRIBUTES); + obj->sa.lpSecurityDescriptor = &obj->sd; + obj->sa.bInheritHandle = TRUE; + if (!InitializeSecurityDescriptor (&obj->sd, SECURITY_DESCRIPTOR_REVISION)) + return false; + if (!SetSecurityDescriptorDacl (&obj->sd, TRUE, NULL, FALSE)) + return false; + return true; +} + +/* + * This event is initially created in the non-signaled + * state. It will transition to the signaled state when + * we have received a terminate signal from the Service + * Control Manager which will cause an asynchronous call + * of ServiceStop below. + */ +#define EXIT_EVENT_NAME TEXT(PACKAGE "_exit_1") + +HANDLE +create_event (LPCTSTR name, bool allow_all, bool initial_state, bool manual_reset) +{ + if (allow_all) + { + struct security_attributes sa; + if (!init_security_attributes_allow_all (&sa)) + return NULL; + return CreateEvent (&sa.sa, (BOOL)manual_reset, (BOOL)initial_state, name); + } + else + return CreateEvent (NULL, (BOOL)manual_reset, (BOOL)initial_state, name); +} + +void +close_if_open (HANDLE h) +{ + if (h != NULL) + CloseHandle (h); +} + +static bool +match (const WIN32_FIND_DATA *find, LPCTSTR ext) +{ + int i; + + if (find->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + return false; + + if (!_tcslen (ext)) + return true; + + i = _tcslen (find->cFileName) - _tcslen (ext) - 1; + if (i < 1) + return false; + + return find->cFileName[i] == '.' && !_tcsicmp (find->cFileName + i + 1, ext); +} + +/* + * Modify the extension on a filename. + */ +static bool +modext (LPTSTR dest, int size, LPCTSTR src, LPCTSTR newext) +{ + int i; + + if (size > 0 && (_tcslen (src) + 1) <= size) + { + _tcscpy (dest, src); + dest [size - 1] = TEXT('\0'); + i = _tcslen (dest); + while (--i >= 0) + { + if (dest[i] == TEXT('\\')) + break; + if (dest[i] == TEXT('.')) + { + dest[i] = TEXT('\0'); + break; + } + } + if (_tcslen (dest) + _tcslen(newext) + 2 <= size) + { + _tcscat (dest, TEXT(".")); + _tcscat (dest, newext); + return true; + } + dest[0] = TEXT('\0'); + } + return false; +} + +static DWORD WINAPI +ServiceCtrlAutomatic (DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx) +{ + SERVICE_STATUS *status = ctx; + switch (ctrl_code) + { + case SERVICE_CONTROL_STOP: + status->dwCurrentState = SERVICE_STOP_PENDING; + ReportStatusToSCMgr (service, status); + if (exit_event) + SetEvent (exit_event); + return NO_ERROR; + + case SERVICE_CONTROL_INTERROGATE: + return NO_ERROR; + + default: + return ERROR_CALL_NOT_IMPLEMENTED; + } +} + + +VOID WINAPI +ServiceStartAutomatic (DWORD dwArgc, LPTSTR *lpszArgv) +{ + DWORD error = NO_ERROR; + settings_t settings; + + service = RegisterServiceCtrlHandlerEx (automatic_service.name, ServiceCtrlAutomatic, &status); + if (!service) + return; + + status.dwServiceType = SERVICE_WIN32_SHARE_PROCESS; + status.dwCurrentState = SERVICE_START_PENDING; + status.dwServiceSpecificExitCode = NO_ERROR; + status.dwWin32ExitCode = NO_ERROR; + status.dwWaitHint = 3000; + + if (!ReportStatusToSCMgr(service, &status)) + { + MsgToEventLog (M_ERR, TEXT("ReportStatusToSCMgr #1 failed")); + goto finish; + } + + /* + * Create our exit event + */ + exit_event = create_event (EXIT_EVENT_NAME, false, false, true); + if (!exit_event) + { + MsgToEventLog (M_ERR, TEXT("CreateEvent failed")); + goto finish; + } + + /* + * If exit event is already signaled, it means we were not + * shut down properly. + */ + if (WaitForSingleObject (exit_event, 0) != WAIT_TIMEOUT) + { + MsgToEventLog (M_ERR, TEXT("Exit event is already signaled -- we were not shut down properly")); + goto finish; + } + + if (!ReportStatusToSCMgr(service, &status)) + { + MsgToEventLog (M_ERR, TEXT("ReportStatusToSCMgr #2 failed")); + goto finish; + } + + /* + * Read info from registry in key HKLM\SOFTWARE\OpenVPN + */ + error = GetOpenvpnSettings (&settings); + if (error != ERROR_SUCCESS) + goto finish; + + /* + * Instantiate an OpenVPN process for each configuration + * file found. + */ + { + WIN32_FIND_DATA find_obj; + HANDLE find_handle; + BOOL more_files; + TCHAR find_string[MAX_PATH]; + + openvpn_sntprintf (find_string, MAX_PATH, TEXT("%s\\*"), settings.config_dir); + + find_handle = FindFirstFile (find_string, &find_obj); + if (find_handle == INVALID_HANDLE_VALUE) + { + MsgToEventLog (M_ERR, TEXT("Cannot get configuration file list using: %s"), find_string); + goto finish; + } + + /* + * Loop over each config file + */ + do { + HANDLE log_handle = NULL; + STARTUPINFO start_info; + PROCESS_INFORMATION proc_info; + struct security_attributes sa; + TCHAR log_file[MAX_PATH]; + TCHAR log_path[MAX_PATH]; + TCHAR command_line[256]; + + CLEAR (start_info); + CLEAR (proc_info); + CLEAR (sa); + + if (!ReportStatusToSCMgr(service, &status)) + { + MsgToEventLog (M_ERR, TEXT("ReportStatusToSCMgr #3 failed")); + FindClose (find_handle); + goto finish; + } + + /* does file have the correct type and extension? */ + if (match (&find_obj, settings.ext_string)) + { + /* get log file pathname */ + if (!modext (log_file, _countof (log_file), find_obj.cFileName, TEXT("log"))) + { + MsgToEventLog (M_ERR, TEXT("Cannot construct logfile name based on: %s"), find_obj.cFileName); + FindClose (find_handle); + goto finish; + } + openvpn_sntprintf (log_path, _countof (log_path), + TEXT("%s\\%s"), settings.log_dir, log_file); + + /* construct command line */ + openvpn_sntprintf (command_line, _countof (command_line), TEXT(PACKAGE " --service %s 1 --config \"%s\""), + EXIT_EVENT_NAME, + find_obj.cFileName); + + /* Make security attributes struct for logfile handle so it can + be inherited. */ + if (!init_security_attributes_allow_all (&sa)) + { + error = MsgToEventLog (M_SYSERR, TEXT("InitializeSecurityDescriptor start_" PACKAGE " failed")); + goto finish; + } + + /* open logfile as stdout/stderr for soon-to-be-spawned subprocess */ + log_handle = CreateFile (log_path, + GENERIC_WRITE, + FILE_SHARE_READ, + &sa.sa, + settings.append ? OPEN_ALWAYS : CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (log_handle == INVALID_HANDLE_VALUE) + { + error = MsgToEventLog (M_SYSERR, TEXT("Cannot open logfile: %s"), log_path); + FindClose (find_handle); + goto finish; + } + + /* append to logfile? */ + if (settings.append) + { + if (SetFilePointer (log_handle, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER) + { + error = MsgToEventLog (M_SYSERR, TEXT("Cannot seek to end of logfile: %s"), log_path); + FindClose (find_handle); + goto finish; + } + } + + /* fill in STARTUPINFO struct */ + GetStartupInfo(&start_info); + start_info.cb = sizeof(start_info); + start_info.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; + start_info.wShowWindow = SW_HIDE; + start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + start_info.hStdOutput = start_info.hStdError = log_handle; + + /* create an OpenVPN process for one config file */ + if (!CreateProcess(settings.exe_path, + command_line, + NULL, + NULL, + TRUE, + settings.priority | CREATE_NEW_CONSOLE, + NULL, + settings.config_dir, + &start_info, + &proc_info)) + { + error = MsgToEventLog (M_SYSERR, TEXT("CreateProcess failed, exe='%s' cmdline='%s' dir='%s'"), + settings.exe_path, + command_line, + settings.config_dir); + + FindClose (find_handle); + CloseHandle (log_handle); + goto finish; + } + + /* close unneeded handles */ + Sleep (1000); /* try to prevent race if we close logfile + handle before child process DUPs it */ + if (!CloseHandle (proc_info.hProcess) + || !CloseHandle (proc_info.hThread) + || !CloseHandle (log_handle)) + { + error = MsgToEventLog (M_SYSERR, TEXT("CloseHandle failed")); + goto finish; + } + } + + /* more files to process? */ + more_files = FindNextFile (find_handle, &find_obj); + + } while (more_files); + + FindClose (find_handle); + } + + /* we are now fully started */ + status.dwCurrentState = SERVICE_RUNNING; + status.dwWaitHint = 0; + if (!ReportStatusToSCMgr(service, &status)) + { + MsgToEventLog (M_ERR, TEXT("ReportStatusToSCMgr SERVICE_RUNNING failed")); + goto finish; + } + + /* wait for our shutdown signal */ + if (WaitForSingleObject (exit_event, INFINITE) != WAIT_OBJECT_0) + MsgToEventLog (M_ERR, TEXT("wait for shutdown signal failed")); + +finish: + if (exit_event) + CloseHandle (exit_event); + + status.dwCurrentState = SERVICE_STOPPED; + status.dwWin32ExitCode = error; + ReportStatusToSCMgr (service, &status); +} diff --git a/src/openvpnserv/common.c b/src/openvpnserv/common.c new file mode 100644 index 0000000..dba4724 --- /dev/null +++ b/src/openvpnserv/common.c @@ -0,0 +1,218 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2011 Heiko Hund <heiko.hund@sophos.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <service.h> +#include <validate.h> +/* + * These are necessary due to certain buggy implementations of (v)snprintf, + * that don't guarantee null termination for size > 0. + */ +int +openvpn_vsntprintf (LPTSTR str, size_t size, LPCTSTR format, va_list arglist) +{ + int len = -1; + if (size > 0) + { + len = _vsntprintf (str, size, format, arglist); + str[size - 1] = 0; + } + return (len >= 0 && len < size); +} +int +openvpn_sntprintf (LPTSTR str, size_t size, LPCTSTR format, ...) +{ + va_list arglist; + int len = -1; + if (size > 0) + { + va_start (arglist, format); + len = openvpn_vsntprintf (str, size, format, arglist); + va_end (arglist); + } + return len; +} + +#define REG_KEY TEXT("SOFTWARE\\" PACKAGE_NAME) + +static DWORD +GetRegString (HKEY key, LPCTSTR value, LPTSTR data, DWORD size) +{ + DWORD type; + LONG status = RegQueryValueEx (key, value, NULL, &type, (LPBYTE) data, &size); + + if (status == ERROR_SUCCESS && type != REG_SZ) + status = ERROR_DATATYPE_MISMATCH; + + if (status != ERROR_SUCCESS) + { + SetLastError (status); + return MsgToEventLog (M_SYSERR, TEXT("Error querying registry value: HKLM\\%s\\%s"), REG_KEY, value); + } + + return ERROR_SUCCESS; +} + + +DWORD +GetOpenvpnSettings (settings_t *s) +{ + TCHAR priority[64]; + TCHAR append[2]; + DWORD error; + HKEY key; + + LONG status = RegOpenKeyEx (HKEY_LOCAL_MACHINE, REG_KEY, 0, KEY_READ, &key); + if (status != ERROR_SUCCESS) + { + SetLastError (status); + return MsgToEventLog (M_SYSERR, TEXT("Could not open Registry key HKLM\\%s not found"), REG_KEY); + } + + error = GetRegString (key, TEXT("exe_path"), s->exe_path, sizeof (s->exe_path)); + if (error != ERROR_SUCCESS) + goto out; + + error = GetRegString (key, TEXT("config_dir"), s->config_dir, sizeof (s->config_dir)); + if (error != ERROR_SUCCESS) + goto out; + + error = GetRegString (key, TEXT("config_ext"), s->ext_string, sizeof (s->ext_string)); + if (error != ERROR_SUCCESS) + goto out; + + error = GetRegString (key, TEXT("log_dir"), s->log_dir, sizeof (s->log_dir)); + if (error != ERROR_SUCCESS) + goto out; + + error = GetRegString (key, TEXT("priority"), priority, sizeof (priority)); + if (error != ERROR_SUCCESS) + goto out; + + error = GetRegString (key, TEXT("log_append"), append, sizeof (append)); + if (error != ERROR_SUCCESS) + goto out; + + /* read if present, else use default */ + error = GetRegString (key, TEXT("ovpn_admin_group"), s->ovpn_admin_group, sizeof (s->ovpn_admin_group)); + if (error != ERROR_SUCCESS) + { + openvpn_sntprintf(s->ovpn_admin_group, _countof(s->ovpn_admin_group), OVPN_ADMIN_GROUP); + error = 0; /* this error is not fatal */ + } + /* set process priority */ + if (!_tcsicmp (priority, TEXT("IDLE_PRIORITY_CLASS"))) + s->priority = IDLE_PRIORITY_CLASS; + else if (!_tcsicmp (priority, TEXT("BELOW_NORMAL_PRIORITY_CLASS"))) + s->priority = BELOW_NORMAL_PRIORITY_CLASS; + else if (!_tcsicmp (priority, TEXT("NORMAL_PRIORITY_CLASS"))) + s->priority = NORMAL_PRIORITY_CLASS; + else if (!_tcsicmp (priority, TEXT("ABOVE_NORMAL_PRIORITY_CLASS"))) + s->priority = ABOVE_NORMAL_PRIORITY_CLASS; + else if (!_tcsicmp (priority, TEXT("HIGH_PRIORITY_CLASS"))) + s->priority = HIGH_PRIORITY_CLASS; + else + { + SetLastError (ERROR_INVALID_DATA); + error = MsgToEventLog (M_SYSERR, TEXT("Unknown priority name: %s"), priority); + goto out; + } + + /* set log file append/truncate flag */ + if (append[0] == TEXT('0')) + s->append = FALSE; + else if (append[0] == TEXT('1')) + s->append = TRUE; + else + { + SetLastError (ERROR_INVALID_DATA); + error = MsgToEventLog (M_ERR, TEXT("Log file append flag (given as '%s') must be '0' or '1'"), append); + goto out; + } + +out: + RegCloseKey (key); + return error; +} + + +LPCTSTR +GetLastErrorText () +{ + static TCHAR buf[256]; + DWORD len; + LPTSTR tmp = NULL; + + len = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY, + NULL, GetLastError(), LANG_NEUTRAL, (LPTSTR)&tmp, 0, NULL); + + if (len == 0 || (long) _countof (buf) < (long) len + 14) + buf[0] = TEXT('\0'); + else + { + tmp[_tcslen (tmp) - 2] = TEXT('\0'); /* remove CR/LF characters */ + openvpn_sntprintf (buf, _countof (buf), TEXT("%s (0x%x)"), tmp, GetLastError()); + } + + if (tmp) + LocalFree (tmp); + + return buf; +} + + +DWORD +MsgToEventLog (DWORD flags, LPCTSTR format, ...) +{ + HANDLE hEventSource; + TCHAR msg[2][256]; + DWORD error = 0; + LPCTSTR err_msg = TEXT(""); + va_list arglist; + + if (flags & MSG_FLAGS_SYS_CODE) + { + error = GetLastError (); + err_msg = GetLastErrorText (); + } + + hEventSource = RegisterEventSource (NULL, APPNAME); + if (hEventSource != NULL) + { + openvpn_sntprintf (msg[0], _countof (msg[0]), + TEXT("%s%s: %s"), APPNAME, + (flags & MSG_FLAGS_ERROR) ? TEXT(" error") : TEXT(""), err_msg); + + va_start (arglist, format); + openvpn_vsntprintf (msg[1], _countof (msg[1]), format, arglist); + va_end (arglist); + + const TCHAR *mesg[] = { msg[0], msg[1] }; + ReportEvent (hEventSource, flags & MSG_FLAGS_ERROR ? + EVENTLOG_ERROR_TYPE : EVENTLOG_INFORMATION_TYPE, + 0, 0, NULL, 2, 0, mesg, NULL); + DeregisterEventSource (hEventSource); + } + + return error; +} diff --git a/src/openvpnserv/interactive.c b/src/openvpnserv/interactive.c new file mode 100644 index 0000000..ffaa171 --- /dev/null +++ b/src/openvpnserv/interactive.c @@ -0,0 +1,1652 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2012 Heiko Hund <heiko.hund@sophos.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "service.h" + +#include <winsock2.h> +#include <ws2tcpip.h> +#include <iphlpapi.h> +#include <userenv.h> +#include <accctrl.h> +#include <aclapi.h> +#include <stdio.h> +#include <sddl.h> +#include <shellapi.h> + +#include "openvpn-msg.h" +#include "validate.h" +#include "block_dns.h" + +#define IO_TIMEOUT 2000 /*ms*/ + +#define ERROR_OPENVPN_STARTUP 0x20000000 +#define ERROR_STARTUP_DATA 0x20000001 +#define ERROR_MESSAGE_DATA 0x20000002 +#define ERROR_MESSAGE_TYPE 0x20000003 + +static SERVICE_STATUS_HANDLE service; +static SERVICE_STATUS status; +static HANDLE exit_event = NULL; +static settings_t settings; +static HANDLE rdns_semaphore = NULL; +#define RDNS_TIMEOUT 600 /* seconds to wait for the semaphore */ + + +openvpn_service_t interactive_service = { + interactive, + TEXT(PACKAGE_NAME "ServiceInteractive"), + TEXT(PACKAGE_NAME " Interactive Service"), + TEXT(SERVICE_DEPENDENCIES), + SERVICE_AUTO_START +}; + + +typedef struct { + WCHAR *directory; + WCHAR *options; + WCHAR *std_input; +} STARTUP_DATA; + + +/* Datatype for linked lists */ +typedef struct _list_item { + struct _list_item *next; + LPVOID data; +} list_item_t; + + +/* Datatypes for undo information */ +typedef enum { + address, + route, + block_dns, + _undo_type_max +} undo_type_t; +typedef list_item_t* undo_lists_t[_undo_type_max]; + + +static DWORD +AddListItem (list_item_t **pfirst, LPVOID data) +{ + list_item_t *new_item = malloc (sizeof (list_item_t)); + if (new_item == NULL) + return ERROR_OUTOFMEMORY; + + new_item->next = *pfirst; + new_item->data = data; + + *pfirst = new_item; + return NO_ERROR; +} + +typedef BOOL (*match_fn_t) (LPVOID item, LPVOID ctx); + +static LPVOID +RemoveListItem (list_item_t **pfirst, match_fn_t match, LPVOID ctx) +{ + LPVOID data = NULL; + list_item_t **pnext; + + for (pnext = pfirst; *pnext; pnext = &(*pnext)->next) + { + list_item_t *item = *pnext; + if (!match (item->data, ctx)) + continue; + + /* Found item, remove from the list and free memory */ + *pnext = item->next; + data = item->data; + free (item); + break; + } + return data; +} + + +static HANDLE +CloseHandleEx (LPHANDLE handle) +{ + if (handle && *handle && *handle != INVALID_HANDLE_VALUE) + { + CloseHandle (*handle); + *handle = INVALID_HANDLE_VALUE; + } + return INVALID_HANDLE_VALUE; +} + + +static HANDLE +InitOverlapped (LPOVERLAPPED overlapped) +{ + ZeroMemory (overlapped, sizeof (OVERLAPPED)); + overlapped->hEvent = CreateEvent (NULL, TRUE, FALSE, NULL); + return overlapped->hEvent; +} + + +static BOOL +ResetOverlapped (LPOVERLAPPED overlapped) +{ + HANDLE io_event = overlapped->hEvent; + if (!ResetEvent (io_event)) + return FALSE; + ZeroMemory (overlapped, sizeof (OVERLAPPED)); + overlapped->hEvent = io_event; + return TRUE; +} + + +typedef enum { + peek, + read, + write +} async_op_t; + +static DWORD +AsyncPipeOp (async_op_t op, HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE events) +{ + int i; + BOOL success; + HANDLE io_event; + DWORD res, bytes = 0; + OVERLAPPED overlapped; + LPHANDLE handles = NULL; + + io_event = InitOverlapped (&overlapped); + if (!io_event) + goto out; + + handles = malloc ((count + 1) * sizeof (HANDLE)); + if (!handles) + goto out; + + if (op == write) + success = WriteFile (pipe, buffer, size, NULL, &overlapped); + else + success = ReadFile (pipe, buffer, size, NULL, &overlapped); + if (!success && GetLastError () != ERROR_IO_PENDING && GetLastError () != ERROR_MORE_DATA) + goto out; + + handles[0] = io_event; + for (i = 0; i < count; i++) + handles[i + 1] = events[i]; + + res = WaitForMultipleObjects (count + 1, handles, FALSE, + op == peek ? INFINITE : IO_TIMEOUT); + if (res != WAIT_OBJECT_0) + { + CancelIo (pipe); + goto out; + } + + if (op == peek) + PeekNamedPipe (pipe, NULL, 0, NULL, &bytes, NULL); + else + GetOverlappedResult (pipe, &overlapped, &bytes, TRUE); + +out: + CloseHandleEx (&io_event); + free (handles); + return bytes; +} + +static DWORD +PeekNamedPipeAsync (HANDLE pipe, DWORD count, LPHANDLE events) +{ + return AsyncPipeOp (peek, pipe, NULL, 0, count, events); +} + +static DWORD +ReadPipeAsync (HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE events) +{ + return AsyncPipeOp (read, pipe, buffer, size, count, events); +} + +static DWORD +WritePipeAsync (HANDLE pipe, LPVOID data, DWORD size, DWORD count, LPHANDLE events) +{ + return AsyncPipeOp (write, pipe, data, size, count, events); +} + +static VOID +ReturnProcessId (HANDLE pipe, DWORD pid, DWORD count, LPHANDLE events) +{ + const WCHAR msg[] = L"Process ID"; + WCHAR buf[22 + _countof(msg)]; /* 10 chars each for error and PID and 2 for line breaks */ + + /* + * Same format as error messages (3 line string) with error = 0 in + * 0x%08x format, PID on line 2 and a description "Process ID" on line 3 + */ + _snwprintf (buf, _countof(buf), L"0x%08x\n0x%08x\n%s", 0, pid, msg); + buf[_countof(buf) - 1] = '\0'; + + WritePipeAsync (pipe, buf, wcslen (buf) * 2, count, events); +} + +static VOID +ReturnError (HANDLE pipe, DWORD error, LPCWSTR func, DWORD count, LPHANDLE events) +{ + DWORD result_len; + LPWSTR result = L"0xffffffff\nFormatMessage failed\nCould not return result"; + DWORD_PTR args[] = { + (DWORD_PTR) error, + (DWORD_PTR) func, + (DWORD_PTR) "" + }; + + if (error != ERROR_OPENVPN_STARTUP) + { + FormatMessageW (FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS, + 0, error, 0, (LPWSTR) &args[2], 0, NULL); + } + + result_len = FormatMessageW (FORMAT_MESSAGE_FROM_STRING | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_ARGUMENT_ARRAY, + L"0x%1!08x!\n%2!s!\n%3!s!", 0, 0, + (LPWSTR) &result, 0, (va_list*) args); + + WritePipeAsync (pipe, result, wcslen (result) * 2, count, events); +#ifdef UNICODE + MsgToEventLog (MSG_FLAGS_ERROR, result); +#else + MsgToEventLog (MSG_FLAGS_ERROR, "%S", result); +#endif + + if (error != ERROR_OPENVPN_STARTUP) + LocalFree ((LPVOID) args[2]); + if (result_len) + LocalFree (result); +} + + +static VOID +ReturnLastError (HANDLE pipe, LPCWSTR func) +{ + ReturnError (pipe, GetLastError (), func, 1, &exit_event); +} + + +static VOID +ReturnOpenvpnOutput (HANDLE pipe, HANDLE ovpn_output, DWORD count, LPHANDLE events) +{ + WCHAR *wide_output = NULL; + CHAR output[512]; + DWORD size; + + ReadFile (ovpn_output, output, sizeof (output), &size, NULL); + if (size == 0) + return; + + wide_output = malloc ((size) * sizeof (WCHAR)); + if (wide_output) + { + MultiByteToWideChar (CP_UTF8, 0, output, size, wide_output, size); + wide_output[size - 1] = 0; + } + + ReturnError (pipe, ERROR_OPENVPN_STARTUP, wide_output, count, events); + free (wide_output); +} + +/* + * Validate options against a white list. Also check the config_file is + * inside the config_dir. The white list is defined in validate.c + * Returns true on success + */ +static BOOL +ValidateOptions (HANDLE pipe, const WCHAR *workdir, const WCHAR *options) +{ + WCHAR **argv; + int argc; + WCHAR buf[256]; + BOOL ret = FALSE; + int i; + const WCHAR *msg1 = L"You have specified a config file location (%s relative to %s)" + " that requires admin approval. This error may be avoided" + " by adding your account to the \"%s\" group"; + + const WCHAR *msg2 = L"You have specified an option (%s) that may be used" + " only with admin approval. This error may be avoided" + " by adding your account to the \"%s\" group"; + + argv = CommandLineToArgvW (options, &argc); + + if (!argv) + { + ReturnLastError (pipe, L"CommandLineToArgvW"); + ReturnError (pipe, ERROR_STARTUP_DATA, L"Cannot validate options", 1, &exit_event); + goto out; + } + + /* Note: argv[0] is the first option */ + if (argc < 1) /* no options */ + { + ret = TRUE; + goto out; + } + + /* + * If only one argument, it is the config file + */ + if (argc == 1) + { + WCHAR *argv_tmp[2] = { L"--config", argv[0] }; + + if (!CheckOption (workdir, 2, argv_tmp, &settings)) + { + snwprintf (buf, _countof(buf), msg1, argv[0], workdir, + settings.ovpn_admin_group); + buf[_countof(buf) - 1] = L'\0'; + ReturnError (pipe, ERROR_STARTUP_DATA, buf, 1, &exit_event); + } + goto out; + } + + for (i = 0; i < argc; ++i) + { + if (!IsOption(argv[i])) + continue; + + if (!CheckOption (workdir, argc-i, &argv[i], &settings)) + { + if (wcscmp(L"--config", argv[i]) == 0 && argc-i > 1) + { + snwprintf (buf, _countof(buf), msg1, argv[i+1], workdir, + settings.ovpn_admin_group); + buf[_countof(buf) - 1] = L'\0'; + ReturnError (pipe, ERROR_STARTUP_DATA, buf, 1, &exit_event); + } + else + { + snwprintf (buf, _countof(buf), msg2, argv[i], + settings.ovpn_admin_group); + buf[_countof(buf) - 1] = L'\0'; + ReturnError (pipe, ERROR_STARTUP_DATA, buf, 1, &exit_event); + } + goto out; + } + } + + /* all options passed */ + ret = TRUE; + +out: + if (argv) + LocalFree (argv); + return ret; +} + +static BOOL +GetStartupData (HANDLE pipe, STARTUP_DATA *sud) +{ + size_t len; + BOOL ret = FALSE; + WCHAR *data = NULL; + DWORD size, bytes, read; + + bytes = PeekNamedPipeAsync (pipe, 1, &exit_event); + if (bytes == 0) + { + MsgToEventLog (M_SYSERR, TEXT("PeekNamedPipeAsync failed")); + ReturnLastError (pipe, L"PeekNamedPipeAsync"); + goto out; + } + + size = bytes / sizeof (*data); + data = malloc (bytes); + if (data == NULL) + { + MsgToEventLog (M_SYSERR, TEXT("malloc failed")); + ReturnLastError (pipe, L"malloc"); + goto out; + } + + read = ReadPipeAsync (pipe, data, bytes, 1, &exit_event); + if (bytes != read) + { + MsgToEventLog (M_SYSERR, TEXT("ReadPipeAsync failed")); + ReturnLastError (pipe, L"ReadPipeAsync"); + goto out; + } + + if (data[size - 1] != 0) + { + MsgToEventLog (M_ERR, TEXT("Startup data is not NULL terminated")); + ReturnError (pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event); + goto out; + } + + sud->directory = data; + len = wcslen (sud->directory) + 1; + size -= len; + if (size <= 0) + { + MsgToEventLog (M_ERR, TEXT("Startup data ends at working directory")); + ReturnError (pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event); + goto out; + } + + sud->options = sud->directory + len; + len = wcslen (sud->options) + 1; + size -= len; + if (size <= 0) + { + MsgToEventLog (M_ERR, TEXT("Startup data ends at command line options")); + ReturnError (pipe, ERROR_STARTUP_DATA, L"GetStartupData", 1, &exit_event); + goto out; + } + + sud->std_input = sud->options + len; + data = NULL; /* don't free data */ + ret = TRUE; + +out: + free (data); + return ret; +} + + +static VOID +FreeStartupData (STARTUP_DATA *sud) +{ + free (sud->directory); +} + + +static SOCKADDR_INET +sockaddr_inet (short family, inet_address_t *addr) +{ + SOCKADDR_INET sa_inet; + ZeroMemory (&sa_inet, sizeof (sa_inet)); + sa_inet.si_family = family; + if (family == AF_INET) + sa_inet.Ipv4.sin_addr = addr->ipv4; + else if (family == AF_INET6) + sa_inet.Ipv6.sin6_addr = addr->ipv6; + return sa_inet; +} + +static DWORD +InterfaceLuid (const char *iface_name, PNET_LUID luid) +{ + NETIO_STATUS status; + LPWSTR wide_name; + int n; + + typedef NETIO_STATUS WINAPI (*ConvertInterfaceAliasToLuidFn) (LPCWSTR, PNET_LUID); + static ConvertInterfaceAliasToLuidFn ConvertInterfaceAliasToLuid = NULL; + if (!ConvertInterfaceAliasToLuid) + { + HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll")); + if (iphlpapi == NULL) + return GetLastError (); + + ConvertInterfaceAliasToLuid = (ConvertInterfaceAliasToLuidFn) GetProcAddress (iphlpapi, "ConvertInterfaceAliasToLuid"); + if (!ConvertInterfaceAliasToLuid) + return GetLastError (); + } + + n = MultiByteToWideChar (CP_UTF8, 0, iface_name, -1, NULL, 0); + wide_name = malloc (n * sizeof (WCHAR)); + MultiByteToWideChar (CP_UTF8, 0, iface_name, -1, wide_name, n); + status = ConvertInterfaceAliasToLuid (wide_name, luid); + free (wide_name); + + return status; +} + +static BOOL +CmpAddress (LPVOID item, LPVOID address) +{ + return memcmp (item, address, sizeof (MIB_UNICASTIPADDRESS_ROW)) == 0 ? TRUE : FALSE; +} + +static DWORD +DeleteAddress (PMIB_UNICASTIPADDRESS_ROW addr_row) +{ + typedef NETIOAPI_API (*DeleteUnicastIpAddressEntryFn) (const PMIB_UNICASTIPADDRESS_ROW); + static DeleteUnicastIpAddressEntryFn DeleteUnicastIpAddressEntry = NULL; + + if (!DeleteUnicastIpAddressEntry) + { + HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll")); + if (iphlpapi == NULL) + return GetLastError (); + + DeleteUnicastIpAddressEntry = (DeleteUnicastIpAddressEntryFn) GetProcAddress (iphlpapi, "DeleteUnicastIpAddressEntry"); + if (!DeleteUnicastIpAddressEntry) + return GetLastError (); + } + + return DeleteUnicastIpAddressEntry (addr_row); +} + +static DWORD +HandleAddressMessage (address_message_t *msg, undo_lists_t *lists) +{ + DWORD err; + PMIB_UNICASTIPADDRESS_ROW addr_row; + BOOL add = msg->header.type == msg_add_address; + + typedef NETIOAPI_API (*CreateUnicastIpAddressEntryFn) (const PMIB_UNICASTIPADDRESS_ROW); + typedef NETIOAPI_API (*InitializeUnicastIpAddressEntryFn) (PMIB_UNICASTIPADDRESS_ROW); + static CreateUnicastIpAddressEntryFn CreateUnicastIpAddressEntry = NULL; + static InitializeUnicastIpAddressEntryFn InitializeUnicastIpAddressEntry = NULL; + + if (!CreateUnicastIpAddressEntry || !InitializeUnicastIpAddressEntry) + { + HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll")); + if (iphlpapi == NULL) + return GetLastError (); + + CreateUnicastIpAddressEntry = (CreateUnicastIpAddressEntryFn) GetProcAddress (iphlpapi, "CreateUnicastIpAddressEntry"); + if (!CreateUnicastIpAddressEntry) + return GetLastError (); + + InitializeUnicastIpAddressEntry = (InitializeUnicastIpAddressEntryFn) GetProcAddress (iphlpapi, "InitializeUnicastIpAddressEntry"); + if (!InitializeUnicastIpAddressEntry) + return GetLastError (); + } + + addr_row = malloc (sizeof (*addr_row)); + if (addr_row == NULL) + return ERROR_OUTOFMEMORY; + + InitializeUnicastIpAddressEntry (addr_row); + addr_row->Address = sockaddr_inet (msg->family, &msg->address); + addr_row->OnLinkPrefixLength = (UINT8) msg->prefix_len; + + if (msg->iface.index != -1) + { + addr_row->InterfaceIndex = msg->iface.index; + } + else + { + NET_LUID luid; + err = InterfaceLuid (msg->iface.name, &luid); + if (err) + goto out; + addr_row->InterfaceLuid = luid; + } + + if (add) + { + err = CreateUnicastIpAddressEntry (addr_row); + if (err) + goto out; + + err = AddListItem (&(*lists)[address], addr_row); + if (err) + DeleteAddress (addr_row); + } + else + { + err = DeleteAddress (addr_row); + if (err) + goto out; + + free (RemoveListItem (&(*lists)[address], CmpAddress, addr_row)); + } + +out: + if (!add || err) + free (addr_row); + + return err; +} + +static BOOL +CmpRoute (LPVOID item, LPVOID route) +{ + return memcmp (item, route, sizeof (MIB_IPFORWARD_ROW2)) == 0 ? TRUE : FALSE; +} + +static DWORD +DeleteRoute (PMIB_IPFORWARD_ROW2 fwd_row) +{ + typedef NETIOAPI_API (*DeleteIpForwardEntry2Fn) (PMIB_IPFORWARD_ROW2); + static DeleteIpForwardEntry2Fn DeleteIpForwardEntry2 = NULL; + + if (!DeleteIpForwardEntry2) + { + HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll")); + if (iphlpapi == NULL) + return GetLastError (); + + DeleteIpForwardEntry2 = (DeleteIpForwardEntry2Fn) GetProcAddress (iphlpapi, "DeleteIpForwardEntry2"); + if (!DeleteIpForwardEntry2) + return GetLastError (); + } + + return DeleteIpForwardEntry2 (fwd_row); +} + +static DWORD +HandleRouteMessage (route_message_t *msg, undo_lists_t *lists) +{ + DWORD err; + PMIB_IPFORWARD_ROW2 fwd_row; + BOOL add = msg->header.type == msg_add_route; + + typedef NETIOAPI_API (*CreateIpForwardEntry2Fn) (PMIB_IPFORWARD_ROW2); + static CreateIpForwardEntry2Fn CreateIpForwardEntry2 = NULL; + + if (!CreateIpForwardEntry2) + { + HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll")); + if (iphlpapi == NULL) + return GetLastError (); + + CreateIpForwardEntry2 = (CreateIpForwardEntry2Fn) GetProcAddress (iphlpapi, "CreateIpForwardEntry2"); + if (!CreateIpForwardEntry2) + return GetLastError (); + } + + fwd_row = malloc (sizeof (*fwd_row)); + if (fwd_row == NULL) + return ERROR_OUTOFMEMORY; + + ZeroMemory (fwd_row, sizeof (*fwd_row)); + fwd_row->ValidLifetime = 0xffffffff; + fwd_row->PreferredLifetime = 0xffffffff; + fwd_row->Protocol = MIB_IPPROTO_NETMGMT; + fwd_row->Metric = msg->metric; + fwd_row->DestinationPrefix.Prefix = sockaddr_inet (msg->family, &msg->prefix); + fwd_row->DestinationPrefix.PrefixLength = (UINT8) msg->prefix_len; + fwd_row->NextHop = sockaddr_inet (msg->family, &msg->gateway); + + if (msg->iface.index != -1) + { + fwd_row->InterfaceIndex = msg->iface.index; + } + else if (strlen (msg->iface.name)) + { + NET_LUID luid; + err = InterfaceLuid (msg->iface.name, &luid); + if (err) + goto out; + fwd_row->InterfaceLuid = luid; + } + + if (add) + { + err = CreateIpForwardEntry2 (fwd_row); + if (err) + goto out; + + err = AddListItem (&(*lists)[route], fwd_row); + if (err) + DeleteRoute (fwd_row); + } + else + { + err = DeleteRoute (fwd_row); + if (err) + goto out; + + free (RemoveListItem (&(*lists)[route], CmpRoute, fwd_row)); + } + +out: + if (!add || err) + free (fwd_row); + + return err; +} + + +static DWORD +HandleFlushNeighborsMessage (flush_neighbors_message_t *msg) +{ + typedef NETIOAPI_API (*FlushIpNetTable2Fn) (ADDRESS_FAMILY, NET_IFINDEX); + static FlushIpNetTable2Fn flush_fn = NULL; + + if (msg->family == AF_INET) + return FlushIpNetTable (msg->iface.index); + + if (!flush_fn) + { + HMODULE iphlpapi = GetModuleHandle (TEXT("iphlpapi.dll")); + if (iphlpapi == NULL) + return GetLastError (); + + flush_fn = (FlushIpNetTable2Fn) GetProcAddress (iphlpapi, "FlushIpNetTable2"); + if (!flush_fn) + { + if (GetLastError () == ERROR_PROC_NOT_FOUND) + return WSAEPFNOSUPPORT; + else + return GetLastError (); + } + } + return flush_fn (msg->family, msg->iface.index); +} + +static void +BlockDNSErrHandler (DWORD err, const char *msg) +{ + TCHAR buf[256]; + LPCTSTR err_str; + + if (!err) return; + + err_str = TEXT("Unknown Win32 Error"); + + if (FormatMessage (FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_ARGUMENT_ARRAY, + NULL, err, 0, buf, sizeof (buf), NULL)) + { + err_str = buf; + } + +#ifdef UNICODE + MsgToEventLog (M_ERR, L"%S (status = %lu): %s", msg, err, err_str); +#else + MsgToEventLog (M_ERR, "%s (status = %lu): %s", msg, err, err_str); +#endif + +} + +/* Use an always-true match_fn to get the head of the list */ +static BOOL +CmpEngine (LPVOID item, LPVOID any) +{ + return TRUE; +} + +static DWORD +HandleBlockDNSMessage (const block_dns_message_t *msg, undo_lists_t *lists) +{ + DWORD err = 0; + HANDLE engine = NULL; + LPCWSTR exe_path; + +#ifdef UNICODE + exe_path = settings.exe_path; +#else + WCHAR wide_path[MAX_PATH]; + MultiByteToWideChar (CP_UTF8, 0, settings.exe_path, MAX_PATH, wide_path, MAX_PATH); + exe_path = wide_path; +#endif + + if (msg->header.type == msg_add_block_dns) + { + err = add_block_dns_filters (&engine, msg->iface.index, exe_path, BlockDNSErrHandler); + if (!err) + err = AddListItem (&(*lists)[block_dns], engine); + } + else + { + engine = RemoveListItem (&(*lists)[block_dns], CmpEngine, NULL); + if (engine) + { + err = delete_block_dns_filters (engine); + engine = NULL; + } + else + MsgToEventLog (M_ERR, TEXT("No previous block DNS filters to delete")); + } + + if (err && engine) + { + delete_block_dns_filters (engine); + } + + return err; +} + +/* + * Execute a command and return its exit code. If timeout > 0, terminate + * the process if still running after timeout milliseconds. In that case + * the return value is the windows error code WAIT_TIMEOUT = 0x102 + */ +static DWORD +ExecCommand (const WCHAR *argv0, const WCHAR *cmdline, DWORD timeout) +{ + DWORD exit_code; + STARTUPINFOW si; + PROCESS_INFORMATION pi; + DWORD proc_flags = CREATE_NO_WINDOW|CREATE_UNICODE_ENVIRONMENT; + WCHAR *cmdline_dup = NULL; + + ZeroMemory (&si, sizeof(si)); + ZeroMemory (&pi, sizeof(pi)); + + si.cb = sizeof(si); + + /* CreateProcess needs a modifiable cmdline: make a copy */ + cmdline_dup = wcsdup (cmdline); + if ( cmdline_dup && CreateProcessW (argv0, cmdline_dup, NULL, NULL, FALSE, + proc_flags, NULL, NULL, &si, &pi) ) + { + WaitForSingleObject (pi.hProcess, timeout ? timeout : INFINITE); + if (!GetExitCodeProcess (pi.hProcess, &exit_code)) + { + MsgToEventLog (M_SYSERR, TEXT("ExecCommand: Error getting exit_code:")); + exit_code = GetLastError(); + } + else if (exit_code == STILL_ACTIVE) + { + exit_code = WAIT_TIMEOUT; /* Windows error code 0x102 */ + + /* kill without impunity */ + TerminateProcess (pi.hProcess, exit_code); + MsgToEventLog (M_ERR, TEXT("ExecCommand: \"%s %s\" killed after timeout"), + argv0, cmdline); + } + else if (exit_code) + MsgToEventLog (M_ERR, TEXT("ExecCommand: \"%s %s\" exited with status = %lu"), + argv0, cmdline, exit_code); + else + MsgToEventLog (M_INFO, TEXT("ExecCommand: \"%s %s\" completed"), argv0, cmdline); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + else + { + exit_code = GetLastError(); + MsgToEventLog (M_SYSERR, TEXT("ExecCommand: could not run \"%s %s\" :"), + argv0, cmdline); + } + + free (cmdline_dup); + return exit_code; +} + +/* + * Entry point for register-dns thread. + */ +static DWORD WINAPI +RegisterDNS (LPVOID unused) +{ + DWORD err; + DWORD i; + WCHAR sys_path[MAX_PATH]; + DWORD timeout = RDNS_TIMEOUT * 1000; /* in milliseconds */ + + /* default paths of net and ipconfig commands */ + WCHAR net[MAX_PATH] = L"C:\\Windows\\system32\\net.exe"; + WCHAR ipcfg[MAX_PATH] = L"C:\\Windows\\system32\\ipconfig.exe"; + + struct + { + WCHAR *argv0; + WCHAR *cmdline; + DWORD timeout; + } cmds [] = { + { net, L"net stop dnscache", timeout }, + { net, L"net start dnscache", timeout }, + { ipcfg, L"ipconfig /flushdns", timeout }, + { ipcfg, L"ipconfig /registerdns", timeout }, + }; + int ncmds = sizeof (cmds) / sizeof (cmds[0]); + + HANDLE wait_handles[2] = {rdns_semaphore, exit_event}; + + if(GetSystemDirectory(sys_path, MAX_PATH)) + { + _snwprintf (net, MAX_PATH, L"%s\\%s", sys_path, L"net.exe"); + net[MAX_PATH-1] = L'\0'; + + _snwprintf (ipcfg, MAX_PATH, L"%s\\%s", sys_path, L"ipconfig.exe"); + ipcfg[MAX_PATH-1] = L'\0'; + } + + if (WaitForMultipleObjects (2, wait_handles, FALSE, timeout) == WAIT_OBJECT_0) + { + /* Semaphore locked */ + for (i = 0; i < ncmds; ++i) + { + ExecCommand (cmds[i].argv0, cmds[i].cmdline, cmds[i].timeout); + } + err = 0; + if ( !ReleaseSemaphore (rdns_semaphore, 1, NULL) ) + err = MsgToEventLog (M_SYSERR, TEXT("RegisterDNS: Failed to release regsiter-dns semaphore:")); + } + else + { + MsgToEventLog (M_ERR, TEXT("RegisterDNS: Failed to lock register-dns semaphore")); + err = ERROR_SEM_TIMEOUT; /* Windows error code 0x79 */ + } + return err; +} + +static DWORD +HandleRegisterDNSMessage (void) +{ + DWORD err; + HANDLE thread = NULL; + + /* Delegate this job to a sub-thread */ + thread = CreateThread (NULL, 0, RegisterDNS, NULL, 0, NULL); + + /* + * We don't add these thread handles to the undo list -- the thread and + * processes it spawns are all supposed to terminate or timeout by themselves. + */ + if (thread) + { + err = 0; + CloseHandle (thread); + } + else + err = GetLastError(); + + return err; +} + +static VOID +HandleMessage (HANDLE pipe, DWORD bytes, DWORD count, LPHANDLE events, undo_lists_t *lists) +{ + DWORD read; + union { + message_header_t header; + address_message_t address; + route_message_t route; + flush_neighbors_message_t flush_neighbors; + block_dns_message_t block_dns; + } msg; + ack_message_t ack = { + .header = { + .type = msg_acknowledgement, + .size = sizeof (ack), + .message_id = -1 + }, + .error_number = ERROR_MESSAGE_DATA + }; + + read = ReadPipeAsync (pipe, &msg, bytes, count, events); + if (read != bytes || read < sizeof (msg.header) || read != msg.header.size) + goto out; + + ack.header.message_id = msg.header.message_id; + + switch (msg.header.type) + { + case msg_add_address: + case msg_del_address: + if (msg.header.size == sizeof (msg.address)) + ack.error_number = HandleAddressMessage (&msg.address, lists); + break; + + case msg_add_route: + case msg_del_route: + if (msg.header.size == sizeof (msg.route)) + ack.error_number = HandleRouteMessage (&msg.route, lists); + break; + + case msg_flush_neighbors: + if (msg.header.size == sizeof (msg.flush_neighbors)) + ack.error_number = HandleFlushNeighborsMessage (&msg.flush_neighbors); + break; + + case msg_add_block_dns: + case msg_del_block_dns: + if (msg.header.size == sizeof (msg.block_dns)) + ack.error_number = HandleBlockDNSMessage (&msg.block_dns, lists); + break; + + case msg_register_dns: + ack.error_number = HandleRegisterDNSMessage (); + break; + + default: + ack.error_number = ERROR_MESSAGE_TYPE; + MsgToEventLog (MSG_FLAGS_ERROR, TEXT("Unknown message type %d"), msg.header.type); + break; + } + +out: + WritePipeAsync (pipe, &ack, sizeof (ack), count, events); +} + + +static VOID +Undo (undo_lists_t *lists) +{ + undo_type_t type; + for (type = 0; type < _undo_type_max; type++) + { + list_item_t **pnext = &(*lists)[type]; + while (*pnext) + { + list_item_t *item = *pnext; + switch (type) + { + case address: + DeleteAddress (item->data); + break; + + case route: + DeleteRoute (item->data); + break; + + case block_dns: + delete_block_dns_filters (item->data); + item->data = NULL; + break; + } + + /* Remove from the list and free memory */ + *pnext = item->next; + free (item->data); + free (item); + } + } +} + +static DWORD WINAPI +RunOpenvpn (LPVOID p) +{ + HANDLE pipe = p; + HANDLE ovpn_pipe, svc_pipe; + PTOKEN_USER svc_user, ovpn_user; + HANDLE svc_token = NULL, imp_token = NULL, pri_token = NULL; + HANDLE stdin_read = NULL, stdin_write = NULL; + HANDLE stdout_write = NULL; + DWORD pipe_mode, len, exit_code = 0; + STARTUP_DATA sud = { 0, 0, 0 }; + STARTUPINFOW startup_info; + PROCESS_INFORMATION proc_info; + LPVOID user_env = NULL; + TCHAR ovpn_pipe_name[36]; + LPCWSTR exe_path; + WCHAR *cmdline = NULL; + size_t cmdline_size; + undo_lists_t undo_lists; + + SECURITY_ATTRIBUTES inheritable = { + .nLength = sizeof (inheritable), + .lpSecurityDescriptor = NULL, + .bInheritHandle = TRUE + }; + + PACL ovpn_dacl; + EXPLICIT_ACCESS ea[2]; + SECURITY_DESCRIPTOR ovpn_sd; + SECURITY_ATTRIBUTES ovpn_sa = { + .nLength = sizeof (ovpn_sa), + .lpSecurityDescriptor = &ovpn_sd, + .bInheritHandle = FALSE + }; + + ZeroMemory (&ea, sizeof (ea)); + ZeroMemory (&startup_info, sizeof (startup_info)); + ZeroMemory (&undo_lists, sizeof (undo_lists)); + ZeroMemory (&proc_info, sizeof (proc_info)); + + if (!GetStartupData (pipe, &sud)) + goto out; + + if (!InitializeSecurityDescriptor (&ovpn_sd, SECURITY_DESCRIPTOR_REVISION)) + { + ReturnLastError (pipe, L"InitializeSecurityDescriptor"); + goto out; + } + + /* Get SID of user the service is running under */ + if (!OpenProcessToken (GetCurrentProcess (), TOKEN_QUERY, &svc_token)) + { + ReturnLastError (pipe, L"OpenProcessToken"); + goto out; + } + len = 0; + svc_user = NULL; + while (!GetTokenInformation (svc_token, TokenUser, svc_user, len, &len)) + { + if (GetLastError () != ERROR_INSUFFICIENT_BUFFER) + { + ReturnLastError (pipe, L"GetTokenInformation (service token)"); + goto out; + } + free (svc_user); + svc_user = malloc (len); + if (svc_user == NULL) + { + ReturnLastError (pipe, L"malloc (service token user)"); + goto out; + } + } + if (!IsValidSid (svc_user->User.Sid)) + { + ReturnLastError (pipe, L"IsValidSid (service token user)"); + goto out; + } + + if (!ImpersonateNamedPipeClient (pipe)) + { + ReturnLastError (pipe, L"ImpersonateNamedPipeClient"); + goto out; + } + if (!OpenThreadToken (GetCurrentThread (), TOKEN_ALL_ACCESS, FALSE, &imp_token)) + { + ReturnLastError (pipe, L"OpenThreadToken"); + goto out; + } + len = 0; + ovpn_user = NULL; + while (!GetTokenInformation (imp_token, TokenUser, ovpn_user, len, &len)) + { + if (GetLastError () != ERROR_INSUFFICIENT_BUFFER) + { + ReturnLastError (pipe, L"GetTokenInformation (impersonation token)"); + goto out; + } + free (ovpn_user); + ovpn_user = malloc (len); + if (ovpn_user == NULL) + { + ReturnLastError (pipe, L"malloc (impersonation token user)"); + goto out; + } + } + if (!IsValidSid (ovpn_user->User.Sid)) + { + ReturnLastError (pipe, L"IsValidSid (impersonation token user)"); + goto out; + } + + /* Check user is authorized or options are white-listed */ + if (!IsAuthorizedUser (ovpn_user->User.Sid, &settings) && + !ValidateOptions (pipe, sud.directory, sud.options)) + { + goto out; + } + + /* OpenVPN process DACL entry for access by service and user */ + ea[0].grfAccessPermissions = SPECIFIC_RIGHTS_ALL | STANDARD_RIGHTS_ALL; + ea[0].grfAccessMode = SET_ACCESS; + ea[0].grfInheritance = NO_INHERITANCE; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[0].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN; + ea[0].Trustee.ptstrName = (LPTSTR) svc_user->User.Sid; + ea[1].grfAccessPermissions = READ_CONTROL | SYNCHRONIZE | PROCESS_VM_READ | + SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION; + ea[1].grfAccessMode = SET_ACCESS; + ea[1].grfInheritance = NO_INHERITANCE; + ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[1].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN; + ea[1].Trustee.ptstrName = (LPTSTR) ovpn_user->User.Sid; + + /* Set owner and DACL of OpenVPN security descriptor */ + if (!SetSecurityDescriptorOwner (&ovpn_sd, svc_user->User.Sid, FALSE)) + { + ReturnLastError (pipe, L"SetSecurityDescriptorOwner"); + goto out; + } + if (SetEntriesInAcl (2, ea, NULL, &ovpn_dacl) != ERROR_SUCCESS) + { + ReturnLastError (pipe, L"SetEntriesInAcl"); + goto out; + } + if (!SetSecurityDescriptorDacl (&ovpn_sd, TRUE, ovpn_dacl, FALSE)) + { + ReturnLastError (pipe, L"SetSecurityDescriptorDacl"); + goto out; + } + + /* Create primary token from impersonation token */ + if (!DuplicateTokenEx (imp_token, TOKEN_ALL_ACCESS, NULL, 0, TokenPrimary, &pri_token)) + { + ReturnLastError (pipe, L"DuplicateTokenEx"); + goto out; + } + + /* use /dev/null for stdout of openvpn (client should use --log for output) */ + stdout_write = CreateFile(_T("NUL"), GENERIC_WRITE, FILE_SHARE_WRITE, + &inheritable, OPEN_EXISTING, 0, NULL); + if (stdout_write == INVALID_HANDLE_VALUE) + { + ReturnLastError (pipe, L"CreateFile for stdout"); + goto out; + } + + if (!CreatePipe(&stdin_read, &stdin_write, &inheritable, 0) || + !SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0)) + { + ReturnLastError (pipe, L"CreatePipe"); + goto out; + } + + openvpn_sntprintf (ovpn_pipe_name, _countof (ovpn_pipe_name), + TEXT("\\\\.\\pipe\\openvpn\\service_%lu"), GetCurrentThreadId ()); + ovpn_pipe = CreateNamedPipe (ovpn_pipe_name, + PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 128, 128, 0, NULL); + if (ovpn_pipe == INVALID_HANDLE_VALUE) + { + ReturnLastError (pipe, L"CreateNamedPipe"); + goto out; + } + + svc_pipe = CreateFile (ovpn_pipe_name, GENERIC_READ | GENERIC_WRITE, 0, + &inheritable, OPEN_EXISTING, 0, NULL); + if (svc_pipe == INVALID_HANDLE_VALUE) + { + ReturnLastError (pipe, L"CreateFile"); + goto out; + } + + pipe_mode = PIPE_READMODE_MESSAGE; + if (!SetNamedPipeHandleState (svc_pipe, &pipe_mode, NULL, NULL)) + { + ReturnLastError (pipe, L"SetNamedPipeHandleState"); + goto out; + } + + cmdline_size = wcslen (sud.options) + 128; + cmdline = malloc (cmdline_size * sizeof (*cmdline)); + if (cmdline == NULL) + { + ReturnLastError (pipe, L"malloc"); + goto out; + } + openvpn_sntprintf (cmdline, cmdline_size, L"openvpn %s --msg-channel %lu", + sud.options, svc_pipe); + + if (!CreateEnvironmentBlock (&user_env, imp_token, FALSE)) + { + ReturnLastError (pipe, L"CreateEnvironmentBlock"); + goto out; + } + + startup_info.cb = sizeof (startup_info); + startup_info.lpDesktop = L"winsta0\\default"; + startup_info.dwFlags = STARTF_USESTDHANDLES; + startup_info.hStdInput = stdin_read; + startup_info.hStdOutput = stdout_write; + startup_info.hStdError = stdout_write; + +#ifdef UNICODE + exe_path = settings.exe_path; +#else + WCHAR wide_path[MAX_PATH]; + MultiByteToWideChar (CP_UTF8, 0, settings.exe_path, MAX_PATH, wide_path, MAX_PATH); + exe_path = wide_path; +#endif + + // TODO: make sure HKCU is correct or call LoadUserProfile() + if (!CreateProcessAsUserW (pri_token, exe_path, cmdline, &ovpn_sa, NULL, TRUE, + settings.priority | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, + user_env, sud.directory, &startup_info, &proc_info)) + { + ReturnLastError (pipe, L"CreateProcessAsUser"); + goto out; + } + + if (!RevertToSelf ()) + { + TerminateProcess (proc_info.hProcess, 1); + ReturnLastError (pipe, L"RevertToSelf"); + goto out; + } + + ReturnProcessId (pipe, proc_info.dwProcessId, 1, &exit_event); + + CloseHandleEx (&stdout_write); + CloseHandleEx (&stdin_read); + CloseHandleEx (&svc_pipe); + + DWORD input_size = WideCharToMultiByte (CP_UTF8, 0, sud.std_input, -1, NULL, 0, NULL, NULL); + LPSTR input = NULL; + if (input_size && (input = malloc (input_size))) + { + DWORD written; + WideCharToMultiByte (CP_UTF8, 0, sud.std_input, -1, input, input_size, NULL, NULL); + WriteFile (stdin_write, input, strlen (input), &written, NULL); + free (input); + } + + while (TRUE) + { + DWORD bytes = PeekNamedPipeAsync (ovpn_pipe, 1, &exit_event); + if (bytes == 0) + break; + + HandleMessage (ovpn_pipe, bytes, 1, &exit_event, &undo_lists); + } + + WaitForSingleObject (proc_info.hProcess, IO_TIMEOUT); + GetExitCodeProcess (proc_info.hProcess, &exit_code); + if (exit_code == STILL_ACTIVE) + TerminateProcess (proc_info.hProcess, 1); + else if (exit_code != 0) + { + WCHAR buf[256]; + int len = _snwprintf (buf, _countof (buf), + L"OpenVPN exited with error: exit code = %lu", exit_code); + buf[_countof (buf) - 1] = L'\0'; + ReturnError (pipe, ERROR_OPENVPN_STARTUP, buf, 1, &exit_event); + } + Undo (&undo_lists); + +out: + FlushFileBuffers (pipe); + DisconnectNamedPipe (pipe); + + free (ovpn_user); + free (svc_user); + free (cmdline); + DestroyEnvironmentBlock (user_env); + FreeStartupData (&sud); + CloseHandleEx (&proc_info.hProcess); + CloseHandleEx (&proc_info.hThread); + CloseHandleEx (&stdin_read); + CloseHandleEx (&stdin_write); + CloseHandleEx (&stdout_write); + CloseHandleEx (&svc_token); + CloseHandleEx (&imp_token); + CloseHandleEx (&pri_token); + CloseHandleEx (&ovpn_pipe); + CloseHandleEx (&svc_pipe); + CloseHandleEx (&pipe); + + return 0; +} + + +static DWORD WINAPI +ServiceCtrlInteractive (DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx) +{ + SERVICE_STATUS *status = ctx; + switch (ctrl_code) + { + case SERVICE_CONTROL_STOP: + status->dwCurrentState = SERVICE_STOP_PENDING; + ReportStatusToSCMgr (service, status); + if (exit_event) + SetEvent (exit_event); + return NO_ERROR; + + case SERVICE_CONTROL_INTERROGATE: + return NO_ERROR; + + default: + return ERROR_CALL_NOT_IMPLEMENTED; + } +} + + +static HANDLE +CreateClientPipeInstance (VOID) +{ + HANDLE pipe = NULL; + PACL old_dacl, new_dacl; + PSECURITY_DESCRIPTOR sd; + static EXPLICIT_ACCESS ea[2]; + static BOOL initialized = FALSE; + DWORD flags = PIPE_ACCESS_DUPLEX | WRITE_DAC | FILE_FLAG_OVERLAPPED; + + if (!initialized) + { + PSID everyone, anonymous; + + ConvertStringSidToSid (TEXT("S-1-1-0"), &everyone); + ConvertStringSidToSid (TEXT("S-1-5-7"), &anonymous); + + ea[0].grfAccessPermissions = FILE_GENERIC_WRITE; + ea[0].grfAccessMode = GRANT_ACCESS; + ea[0].grfInheritance = NO_INHERITANCE; + ea[0].Trustee.pMultipleTrustee = NULL; + ea[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[0].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN; + ea[0].Trustee.ptstrName = (LPTSTR) everyone; + + ea[1].grfAccessPermissions = 0; + ea[1].grfAccessMode = REVOKE_ACCESS; + ea[1].grfInheritance = NO_INHERITANCE; + ea[1].Trustee.pMultipleTrustee = NULL; + ea[1].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[1].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN; + ea[1].Trustee.ptstrName = (LPTSTR) anonymous; + + flags |= FILE_FLAG_FIRST_PIPE_INSTANCE; + initialized = TRUE; + } + + pipe = CreateNamedPipe (TEXT("\\\\.\\pipe\\openvpn\\service"), flags, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, + PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, NULL); + if (pipe == INVALID_HANDLE_VALUE) + { + MsgToEventLog (M_SYSERR, TEXT("Could not create named pipe")); + return INVALID_HANDLE_VALUE; + } + + if (GetSecurityInfo (pipe, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, + NULL, NULL, &old_dacl, NULL, &sd) != ERROR_SUCCESS) + { + MsgToEventLog (M_SYSERR, TEXT("Could not get pipe security info")); + return CloseHandleEx (&pipe); + } + + if (SetEntriesInAcl (2, ea, old_dacl, &new_dacl) != ERROR_SUCCESS) + { + MsgToEventLog (M_SYSERR, TEXT("Could not set entries in new acl")); + return CloseHandleEx (&pipe); + } + + if (SetSecurityInfo (pipe, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, + NULL, NULL, new_dacl, NULL) != ERROR_SUCCESS) + { + MsgToEventLog (M_SYSERR, TEXT("Could not set pipe security info")); + return CloseHandleEx (&pipe); + } + + return pipe; +} + + +static DWORD +UpdateWaitHandles (LPHANDLE *handles_ptr, LPDWORD count, + HANDLE io_event, HANDLE exit_event, list_item_t *threads) +{ + static DWORD size = 10; + static LPHANDLE handles = NULL; + DWORD pos = 0; + + if (handles == NULL) + { + handles = malloc (size * sizeof (HANDLE)); + *handles_ptr = handles; + if (handles == NULL) + return ERROR_OUTOFMEMORY; + } + + handles[pos++] = io_event; + + if (!threads) + handles[pos++] = exit_event; + + while (threads) + { + if (pos == size) + { + LPHANDLE tmp; + size += 10; + tmp = realloc (handles, size * sizeof (HANDLE)); + if (tmp == NULL) + { + size -= 10; + *count = pos; + return ERROR_OUTOFMEMORY; + } + handles = tmp; + *handles_ptr = handles; + } + handles[pos++] = threads->data; + threads = threads->next; + } + + *count = pos; + return NO_ERROR; +} + + +static VOID +FreeWaitHandles (LPHANDLE h) +{ + free (h); +} + + +VOID WINAPI +ServiceStartInteractive (DWORD dwArgc, LPTSTR *lpszArgv) +{ + HANDLE pipe, io_event = NULL; + OVERLAPPED overlapped; + DWORD error = NO_ERROR; + list_item_t *threads = NULL; + PHANDLE handles = NULL; + DWORD handle_count; + BOOL CmpHandle (LPVOID item, LPVOID hnd) { return item == hnd; } + + service = RegisterServiceCtrlHandlerEx (interactive_service.name, ServiceCtrlInteractive, &status); + if (!service) + return; + + status.dwServiceType = SERVICE_WIN32_SHARE_PROCESS; + status.dwCurrentState = SERVICE_START_PENDING; + status.dwServiceSpecificExitCode = NO_ERROR; + status.dwWin32ExitCode = NO_ERROR; + status.dwWaitHint = 3000; + ReportStatusToSCMgr (service, &status); + + /* Read info from registry in key HKLM\SOFTWARE\OpenVPN */ + error = GetOpenvpnSettings (&settings); + if (error != ERROR_SUCCESS) + goto out; + + io_event = InitOverlapped (&overlapped); + exit_event = CreateEvent (NULL, TRUE, FALSE, NULL); + if (!exit_event || !io_event) + { + error = MsgToEventLog (M_SYSERR, TEXT("Could not create event")); + goto out; + } + + rdns_semaphore = CreateSemaphoreW (NULL, 1, 1, NULL); + if (!rdns_semaphore) + { + error = MsgToEventLog (M_SYSERR, TEXT("Could not create semaphore for register-dns")); + goto out; + } + + error = UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads); + if (error != NO_ERROR) + goto out; + + pipe = CreateClientPipeInstance (); + if (pipe == INVALID_HANDLE_VALUE) + goto out; + + status.dwCurrentState = SERVICE_RUNNING; + status.dwWaitHint = 0; + ReportStatusToSCMgr (service, &status); + + while (TRUE) + { + if (ConnectNamedPipe (pipe, &overlapped) == FALSE && + GetLastError () != ERROR_PIPE_CONNECTED && + GetLastError () != ERROR_IO_PENDING) + { + MsgToEventLog (M_SYSERR, TEXT("Could not connect pipe")); + break; + } + + error = WaitForMultipleObjects (handle_count, handles, FALSE, INFINITE); + if (error == WAIT_OBJECT_0) + { + /* Client connected, spawn a worker thread for it */ + HANDLE next_pipe = CreateClientPipeInstance (); + HANDLE thread = CreateThread (NULL, 0, RunOpenvpn, pipe, CREATE_SUSPENDED, NULL); + if (thread) + { + error = AddListItem (&threads, thread); + if (!error) + error = UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads); + if (error) + { + ReturnError (pipe, error, L"Insufficient resources to service new clients", 1, &exit_event); + /* Update wait handles again after removing the last worker thread */ + RemoveListItem (&threads, CmpHandle, thread); + UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads); + TerminateThread (thread, 1); + CloseHandleEx (&thread); + CloseHandleEx (&pipe); + } + else + ResumeThread (thread); + } + else + CloseHandleEx (&pipe); + + ResetOverlapped (&overlapped); + pipe = next_pipe; + } + else + { + CancelIo (pipe); + if (error == WAIT_FAILED) + { + MsgToEventLog (M_SYSERR, TEXT("WaitForMultipleObjects failed")); + SetEvent (exit_event); + /* Give some time for worker threads to exit and then terminate */ + Sleep (1000); + break; + } + if (!threads) + { + /* exit event signaled */ + CloseHandleEx (&pipe); + ResetEvent (exit_event); + error = NO_ERROR; + break; + } + + /* Worker thread ended */ + HANDLE thread = RemoveListItem (&threads, CmpHandle, handles[error]); + UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads); + CloseHandleEx (&thread); + } + } + +out: + FreeWaitHandles (handles); + CloseHandleEx (&io_event); + CloseHandleEx (&exit_event); + CloseHandleEx (&rdns_semaphore); + + status.dwCurrentState = SERVICE_STOPPED; + status.dwWin32ExitCode = error; + ReportStatusToSCMgr (service, &status); +} diff --git a/src/openvpnserv/openvpnserv.c b/src/openvpnserv/openvpnserv.c deleted file mode 100755 index 56f5a02..0000000 --- a/src/openvpnserv/openvpnserv.c +++ /dev/null @@ -1,534 +0,0 @@ -/* - * OpenVPN -- An application to securely tunnel IP networks - * over a single TCP/UDP port, with support for SSL/TLS-based - * session authentication and key exchange, - * packet encryption, packet authentication, and - * packet compression. - * - * Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program (see the file COPYING included with this - * distribution); if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -/* - * This program allows one or more OpenVPN processes to be started - * as a service. To build, you must get the service sample from the - * Platform SDK and replace Simple.c with this file. - * - * You should also apply service.patch to - * service.c and service.h from the Platform SDK service sample. - * - * This code is designed to be built with the mingw compiler. - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#elif defined(_MSC_VER) -#include "config-msvc.h" -#endif -#include <windows.h> -#include <stdlib.h> -#include <stdio.h> -#include <stdarg.h> -#include <process.h> -#include "service.h" - -/* bool definitions */ -#define bool int -#define true 1 -#define false 0 - -/* These are new for 2000/XP, so they aren't in the mingw headers yet */ -#ifndef BELOW_NORMAL_PRIORITY_CLASS -#define BELOW_NORMAL_PRIORITY_CLASS 0x00004000 -#endif -#ifndef ABOVE_NORMAL_PRIORITY_CLASS -#define ABOVE_NORMAL_PRIORITY_CLASS 0x00008000 -#endif - -struct security_attributes -{ - SECURITY_ATTRIBUTES sa; - SECURITY_DESCRIPTOR sd; -}; - -/* - * This event is initially created in the non-signaled - * state. It will transition to the signaled state when - * we have received a terminate signal from the Service - * Control Manager which will cause an asynchronous call - * of ServiceStop below. - */ -#define EXIT_EVENT_NAME PACKAGE "_exit_1" - -/* - * Which registry key in HKLM should - * we get config info from? - */ -#define REG_KEY "SOFTWARE\\" PACKAGE_NAME - -static HANDLE exit_event = NULL; - -/* clear an object */ -#define CLEAR(x) memset(&(x), 0, sizeof(x)) - -/* - * Message handling - */ -#define M_INFO (0) /* informational */ -#define M_SYSERR (MSG_FLAGS_ERROR|MSG_FLAGS_SYS_CODE) /* error + system code */ -#define M_ERR (MSG_FLAGS_ERROR) /* error */ - -/* write error to event log */ -#define MSG(flags, ...) \ - { \ - char x_msg[256]; \ - openvpn_snprintf (x_msg, sizeof(x_msg), __VA_ARGS__); \ - AddToMessageLog ((flags), x_msg); \ - } - -/* get a registry string */ -#define QUERY_REG_STRING(name, data) \ - { \ - len = sizeof (data); \ - status = RegQueryValueEx(openvpn_key, name, NULL, &type, data, &len); \ - if (status != ERROR_SUCCESS || type != REG_SZ) \ - { \ - SetLastError (status); \ - MSG (M_SYSERR, error_format_str, name); \ - RegCloseKey (openvpn_key); \ - goto finish; \ - } \ - } - -/* get a registry string */ -#define QUERY_REG_DWORD(name, data) \ - { \ - len = sizeof (DWORD); \ - status = RegQueryValueEx(openvpn_key, name, NULL, &type, (LPBYTE)&data, &len); \ - if (status != ERROR_SUCCESS || type != REG_DWORD || len != sizeof (DWORD)) \ - { \ - SetLastError (status); \ - MSG (M_SYSERR, error_format_dword, name); \ - RegCloseKey (openvpn_key); \ - goto finish; \ - } \ - } - -/* - * This is necessary due to certain buggy implementations of snprintf, - * that don't guarantee null termination for size > 0. - * (copied from ../buffer.c, line 217) - * (git: 100644 blob e2f8caab0a5b2a870092c6cd508a1a50c21c3ba3 buffer.c) - */ - -int openvpn_snprintf(char *str, size_t size, const char *format, ...) -{ - va_list arglist; - int len = -1; - if (size > 0) - { - va_start (arglist, format); - len = vsnprintf (str, size, format, arglist); - va_end (arglist); - str[size - 1] = 0; - } - return (len >= 0 && len < size); -} - - -bool -init_security_attributes_allow_all (struct security_attributes *obj) -{ - CLEAR (*obj); - - obj->sa.nLength = sizeof (SECURITY_ATTRIBUTES); - obj->sa.lpSecurityDescriptor = &obj->sd; - obj->sa.bInheritHandle = TRUE; - if (!InitializeSecurityDescriptor (&obj->sd, SECURITY_DESCRIPTOR_REVISION)) - return false; - if (!SetSecurityDescriptorDacl (&obj->sd, TRUE, NULL, FALSE)) - return false; - return true; -} - -HANDLE -create_event (const char *name, bool allow_all, bool initial_state, bool manual_reset) -{ - if (allow_all) - { - struct security_attributes sa; - if (!init_security_attributes_allow_all (&sa)) - return NULL; - return CreateEvent (&sa.sa, (BOOL)manual_reset, (BOOL)initial_state, name); - } - else - return CreateEvent (NULL, (BOOL)manual_reset, (BOOL)initial_state, name); -} - -void -close_if_open (HANDLE h) -{ - if (h != NULL) - CloseHandle (h); -} - -static bool -match (const WIN32_FIND_DATA *find, const char *ext) -{ - int i; - - if (find->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - return false; - - if (!strlen (ext)) - return true; - - i = strlen (find->cFileName) - strlen (ext) - 1; - if (i < 1) - return false; - - return find->cFileName[i] == '.' && !_stricmp (find->cFileName + i + 1, ext); -} - -/* - * Modify the extension on a filename. - */ -static bool -modext (char *dest, int size, const char *src, const char *newext) -{ - int i; - - if (size > 0 && (strlen (src) + 1) <= size) - { - strcpy (dest, src); - dest [size - 1] = '\0'; - i = strlen (dest); - while (--i >= 0) - { - if (dest[i] == '\\') - break; - if (dest[i] == '.') - { - dest[i] = '\0'; - break; - } - } - if (strlen (dest) + strlen(newext) + 2 <= size) - { - strcat (dest, "."); - strcat (dest, newext); - return true; - } - dest [0] = '\0'; - } - return false; -} - -VOID ServiceStart (DWORD dwArgc, LPTSTR *lpszArgv) -{ - char exe_path[MAX_PATH]; - char config_dir[MAX_PATH]; - char ext_string[16]; - char log_dir[MAX_PATH]; - char priority_string[64]; - char append_string[2]; - - DWORD priority; - bool append; - - ResetError (); - - if (!ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000)) - { - MSG (M_ERR, "ReportStatusToSCMgr #1 failed"); - goto finish; - } - - /* - * Create our exit event - */ - exit_event = create_event (EXIT_EVENT_NAME, false, false, true); - if (!exit_event) - { - MSG (M_ERR, "CreateEvent failed"); - goto finish; - } - - /* - * If exit event is already signaled, it means we were not - * shut down properly. - */ - if (WaitForSingleObject (exit_event, 0) != WAIT_TIMEOUT) - { - MSG (M_ERR, "Exit event is already signaled -- we were not shut down properly"); - goto finish; - } - - if (!ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000)) - { - MSG (M_ERR, "ReportStatusToSCMgr #2 failed"); - goto finish; - } - - /* - * Read info from registry in key HKLM\SOFTWARE\OpenVPN - */ - { - HKEY openvpn_key; - LONG status; - DWORD len; - DWORD type; - - static const char error_format_str[] = - "Error querying registry key of type REG_SZ: HKLM\\" REG_KEY "\\%s"; - - static const char error_format_dword[] = - "Error querying registry key of type REG_DWORD: HKLM\\" REG_KEY "\\%s"; - - status = RegOpenKeyEx( - HKEY_LOCAL_MACHINE, - REG_KEY, - 0, - KEY_READ, - &openvpn_key); - - if (status != ERROR_SUCCESS) - { - SetLastError (status); - MSG (M_SYSERR, "Registry key HKLM\\" REG_KEY " not found"); - goto finish; - } - - /* get path to openvpn.exe */ - QUERY_REG_STRING ("exe_path", exe_path); - - /* get path to configuration directory */ - QUERY_REG_STRING ("config_dir", config_dir); - - /* get extension on configuration files */ - QUERY_REG_STRING ("config_ext", ext_string); - - /* get path to log directory */ - QUERY_REG_STRING ("log_dir", log_dir); - - /* get priority for spawned OpenVPN subprocesses */ - QUERY_REG_STRING ("priority", priority_string); - - /* should we truncate or append to logfile? */ - QUERY_REG_STRING ("log_append", append_string); - - RegCloseKey (openvpn_key); - } - - /* set process priority */ - priority = NORMAL_PRIORITY_CLASS; - if (!_stricmp (priority_string, "IDLE_PRIORITY_CLASS")) - priority = IDLE_PRIORITY_CLASS; - else if (!_stricmp (priority_string, "BELOW_NORMAL_PRIORITY_CLASS")) - priority = BELOW_NORMAL_PRIORITY_CLASS; - else if (!_stricmp (priority_string, "NORMAL_PRIORITY_CLASS")) - priority = NORMAL_PRIORITY_CLASS; - else if (!_stricmp (priority_string, "ABOVE_NORMAL_PRIORITY_CLASS")) - priority = ABOVE_NORMAL_PRIORITY_CLASS; - else if (!_stricmp (priority_string, "HIGH_PRIORITY_CLASS")) - priority = HIGH_PRIORITY_CLASS; - else - { - MSG (M_ERR, "Unknown priority name: %s", priority_string); - goto finish; - } - - /* set log file append/truncate flag */ - append = false; - if (append_string[0] == '0') - append = false; - else if (append_string[0] == '1') - append = true; - else - { - MSG (M_ERR, "Log file append flag (given as '%s') must be '0' or '1'", append_string); - goto finish; - } - - /* - * Instantiate an OpenVPN process for each configuration - * file found. - */ - { - WIN32_FIND_DATA find_obj; - HANDLE find_handle; - BOOL more_files; - char find_string[MAX_PATH]; - - openvpn_snprintf (find_string, MAX_PATH, "%s\\*", config_dir); - - find_handle = FindFirstFile (find_string, &find_obj); - if (find_handle == INVALID_HANDLE_VALUE) - { - MSG (M_ERR, "Cannot get configuration file list using: %s", find_string); - goto finish; - } - - /* - * Loop over each config file - */ - do { - HANDLE log_handle = NULL; - STARTUPINFO start_info; - PROCESS_INFORMATION proc_info; - struct security_attributes sa; - char log_file[MAX_PATH]; - char log_path[MAX_PATH]; - char command_line[256]; - - CLEAR (start_info); - CLEAR (proc_info); - CLEAR (sa); - - if (!ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000)) - { - MSG (M_ERR, "ReportStatusToSCMgr #3 failed"); - FindClose (find_handle); - goto finish; - } - - /* does file have the correct type and extension? */ - if (match (&find_obj, ext_string)) - { - /* get log file pathname */ - if (!modext (log_file, sizeof (log_file), find_obj.cFileName, "log")) - { - MSG (M_ERR, "Cannot construct logfile name based on: %s", find_obj.cFileName); - FindClose (find_handle); - goto finish; - } - openvpn_snprintf (log_path, sizeof(log_path), - "%s\\%s", log_dir, log_file); - - /* construct command line */ - openvpn_snprintf (command_line, sizeof(command_line), PACKAGE " --service %s 1 --config \"%s\"", - EXIT_EVENT_NAME, - find_obj.cFileName); - - /* Make security attributes struct for logfile handle so it can - be inherited. */ - if (!init_security_attributes_allow_all (&sa)) - { - MSG (M_SYSERR, "InitializeSecurityDescriptor start_" PACKAGE " failed"); - goto finish; - } - - /* open logfile as stdout/stderr for soon-to-be-spawned subprocess */ - log_handle = CreateFile (log_path, - GENERIC_WRITE, - FILE_SHARE_READ, - &sa.sa, - append ? OPEN_ALWAYS : CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL); - - if (log_handle == INVALID_HANDLE_VALUE) - { - MSG (M_SYSERR, "Cannot open logfile: %s", log_path); - FindClose (find_handle); - goto finish; - } - - /* append to logfile? */ - if (append) - { - if (SetFilePointer (log_handle, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER) - { - MSG (M_SYSERR, "Cannot seek to end of logfile: %s", log_path); - FindClose (find_handle); - goto finish; - } - } - - /* fill in STARTUPINFO struct */ - GetStartupInfo(&start_info); - start_info.cb = sizeof(start_info); - start_info.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; - start_info.wShowWindow = SW_HIDE; - start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - start_info.hStdOutput = start_info.hStdError = log_handle; - - /* create an OpenVPN process for one config file */ - if (!CreateProcess(exe_path, - command_line, - NULL, - NULL, - TRUE, - priority | CREATE_NEW_CONSOLE, - NULL, - config_dir, - &start_info, - &proc_info)) - { - MSG (M_SYSERR, "CreateProcess failed, exe='%s' cmdline='%s' dir='%s'", - exe_path, - command_line, - config_dir); - - FindClose (find_handle); - CloseHandle (log_handle); - goto finish; - } - - /* close unneeded handles */ - Sleep (1000); /* try to prevent race if we close logfile - handle before child process DUPs it */ - if (!CloseHandle (proc_info.hProcess) - || !CloseHandle (proc_info.hThread) - || !CloseHandle (log_handle)) - { - MSG (M_SYSERR, "CloseHandle failed"); - goto finish; - } - } - - /* more files to process? */ - more_files = FindNextFile (find_handle, &find_obj); - - } while (more_files); - - FindClose (find_handle); - } - - /* we are now fully started */ - if (!ReportStatusToSCMgr(SERVICE_RUNNING, NO_ERROR, 0)) - { - MSG (M_ERR, "ReportStatusToSCMgr SERVICE_RUNNING failed"); - goto finish; - } - - /* wait for our shutdown signal */ - if (WaitForSingleObject (exit_event, INFINITE) != WAIT_OBJECT_0) - { - MSG (M_ERR, "wait for shutdown signal failed"); - } - - finish: - ServiceStop (); - if (exit_event) - CloseHandle (exit_event); -} - -VOID ServiceStop() -{ - if (exit_event) - SetEvent(exit_event); -} diff --git a/src/openvpnserv/openvpnserv.vcxproj b/src/openvpnserv/openvpnserv.vcxproj index 2a8943d..c6760da 100644 --- a/src/openvpnserv/openvpnserv.vcxproj +++ b/src/openvpnserv/openvpnserv.vcxproj @@ -111,4 +111,4 @@ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Label="ExtensionTargets"> </ImportGroup> -</Project>
\ No newline at end of file +</Project> diff --git a/src/openvpnserv/service.c b/src/openvpnserv/service.c index d7562b3..82f5551 100644 --- a/src/openvpnserv/service.c +++ b/src/openvpnserv/service.c @@ -1,700 +1,245 @@ -/*--------------------------------------------------------------------------- -THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF -ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A -PARTICULAR PURPOSE. - -Copyright (C) 1993 - 2000. Microsoft Corporation. All rights reserved. - -MODULE: service.c - -PURPOSE: Implements functions required by all Windows NT services - -FUNCTIONS: - main(int argc, char **argv); - service_ctrl(DWORD dwCtrlCode); - service_main(DWORD dwArgc, LPTSTR *lpszArgv); - CmdInstallService(); - CmdRemoveService(); - CmdStartService(); - CmdDebugService(int argc, char **argv); - ControlHandler ( DWORD dwCtrlType ); - GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize ); - ----------------------------------------------------------------------------*/ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#elif defined(_MSC_VER) -#include "config-msvc.h" -#endif -#include <windows.h> -#include <stdio.h> -#include <stdlib.h> -#include <process.h> -#include <tchar.h> +/* + * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF + * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED + * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A + * PARTICULAR PURPOSE. + * + * Copyright (C) 1993 - 2000. Microsoft Corporation. All rights reserved. + * 2013 Heiko Hund <heiko.hund@sophos.com> + */ #include "service.h" -// internal variables -SERVICE_STATUS ssStatus; // current status of the service -SERVICE_STATUS_HANDLE sshStatusHandle; -DWORD dwErr = 0; -BOOL bDebug = FALSE; -TCHAR szErr[256]; - -// internal function prototypes -VOID WINAPI service_ctrl(DWORD dwCtrlCode); -VOID WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv); -int CmdInstallService(); -int CmdRemoveService(); -int CmdStartService(); -VOID CmdDebugService(int argc, char **argv); -BOOL WINAPI ControlHandler ( DWORD dwCtrlType ); -LPTSTR GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize ); - -// -// FUNCTION: main -// -// PURPOSE: entrypoint for service -// -// PARAMETERS: -// argc - number of command line arguments -// argv - array of command line arguments -// -// RETURN VALUE: -// none -// -// COMMENTS: -// main() either performs the command line task, or -// call StartServiceCtrlDispatcher to register the -// main service thread. When the this call returns, -// the service has stopped, so exit. -// -int __cdecl main(int argc, char **argv) -{ - SERVICE_TABLE_ENTRY dispatchTable[] = - { - { TEXT(SZSERVICENAME), (LPSERVICE_MAIN_FUNCTION)service_main}, - { NULL, NULL} - }; - - if ( (argc > 1) && - ((*argv[1] == '-') || (*argv[1] == '/')) ) - { - if ( _stricmp( "install", argv[1]+1 ) == 0 ) - { - return CmdInstallService(); - } - else if ( _stricmp( "remove", argv[1]+1 ) == 0 ) - { - return CmdRemoveService(); - } - else if ( _stricmp( "start", argv[1]+1 ) == 0) - { - return CmdStartService(); - } - else if ( _stricmp( "debug", argv[1]+1 ) == 0 ) - { - bDebug = TRUE; - CmdDebugService(argc, argv); - } - else - { - goto dispatch; - } - return 0; - } - - // if it doesn't match any of the above parameters - // the service control manager may be starting the service - // so we must call StartServiceCtrlDispatcher - dispatch: - // this is just to be friendly - printf( "%s -install to install the service\n", SZAPPNAME ); - printf( "%s -start to start the service\n", SZAPPNAME ); - printf( "%s -remove to remove the service\n", SZAPPNAME ); - printf( "%s -debug <params> to run as a console app for debugging\n", SZAPPNAME ); - printf( "\nStartServiceCtrlDispatcher being called.\n" ); - printf( "This may take several seconds. Please wait.\n" ); - - if (!StartServiceCtrlDispatcher(dispatchTable)) - AddToMessageLog(MSG_FLAGS_ERROR, TEXT("StartServiceCtrlDispatcher failed.")); - - return 0; -} - - - -// -// FUNCTION: service_main -// -// PURPOSE: To perform actual initialization of the service -// -// PARAMETERS: -// dwArgc - number of command line arguments -// lpszArgv - array of command line arguments -// -// RETURN VALUE: -// none -// -// COMMENTS: -// This routine performs the service initialization and then calls -// the user defined ServiceStart() routine to perform majority -// of the work. -// -void WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv) -{ - - // register our service control handler: - // - sshStatusHandle = RegisterServiceCtrlHandler( TEXT(SZSERVICENAME), service_ctrl); - - if (!sshStatusHandle) - goto cleanup; - - // SERVICE_STATUS members that don't change in example - // - ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; - ssStatus.dwServiceSpecificExitCode = 0; - - - // report the status to the service control manager. - // - if (!ReportStatusToSCMgr( - SERVICE_START_PENDING, // service state - NO_ERROR, // exit code - 3000)) // wait hint - goto cleanup; - - - ServiceStart( dwArgc, lpszArgv ); - - cleanup: +#include <windows.h> +#include <stdio.h> +#include <process.h> - // try to report the stopped status to the service control manager. - // - if (sshStatusHandle) - (VOID)ReportStatusToSCMgr( - SERVICE_STOPPED, - dwErr, - 0); - return; -} +openvpn_service_t openvpn_service[_service_max]; - -// -// FUNCTION: service_ctrl -// -// PURPOSE: This function is called by the SCM whenever -// ControlService() is called on this service. -// -// PARAMETERS: -// dwCtrlCode - type of control requested -// -// RETURN VALUE: -// none -// -// COMMENTS: -// -VOID WINAPI service_ctrl(DWORD dwCtrlCode) +BOOL +ReportStatusToSCMgr (SERVICE_STATUS_HANDLE service, SERVICE_STATUS *status) { - // Handle the requested control code. - // - switch (dwCtrlCode) - { - // Stop the service. - // - // SERVICE_STOP_PENDING should be reported before - // setting the Stop Event - hServerStopEvent - in - // ServiceStop(). This avoids a race condition - // which may result in a 1053 - The Service did not respond... - // error. - case SERVICE_CONTROL_STOP: - ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 0); - ServiceStop(); - return; - - // Update the service status. - // - case SERVICE_CONTROL_INTERROGATE: - break; - - // invalid control code - // - default: - break; - - } - - ReportStatusToSCMgr(ssStatus.dwCurrentState, NO_ERROR, 0); + static DWORD dwCheckPoint = 1; + BOOL res = TRUE; + + if (status->dwCurrentState == SERVICE_START_PENDING) + status->dwControlsAccepted = 0; + else + status->dwControlsAccepted = SERVICE_ACCEPT_STOP; + + if (status->dwCurrentState == SERVICE_RUNNING || + status->dwCurrentState == SERVICE_STOPPED) + status->dwCheckPoint = 0; + else + status->dwCheckPoint = dwCheckPoint++; + + /* Report the status of the service to the service control manager. */ + res = SetServiceStatus (service, status); + if (!res) + MsgToEventLog(MSG_FLAGS_ERROR, TEXT("SetServiceStatus")); + + return res; } - - -// -// FUNCTION: ReportStatusToSCMgr() -// -// PURPOSE: Sets the current status of the service and -// reports it to the Service Control Manager -// -// PARAMETERS: -// dwCurrentState - the state of the service -// dwWin32ExitCode - error code to report -// dwWaitHint - worst case estimate to next checkpoint -// -// RETURN VALUE: -// TRUE - success -// FALSE - failure -// -// COMMENTS: -// -BOOL ReportStatusToSCMgr(DWORD dwCurrentState, - DWORD dwWin32ExitCode, - DWORD dwWaitHint) +static int +CmdInstallServices () { - static DWORD dwCheckPoint = 1; - BOOL fResult = TRUE; - - - if ( !bDebug ) // when debugging we don't report to the SCM - { - if (dwCurrentState == SERVICE_START_PENDING) - ssStatus.dwControlsAccepted = 0; - else - ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; - - ssStatus.dwCurrentState = dwCurrentState; - ssStatus.dwWin32ExitCode = dwWin32ExitCode; - ssStatus.dwWaitHint = dwWaitHint; + SC_HANDLE service; + SC_HANDLE svc_ctl_mgr; + TCHAR path[512]; + int i, ret = _service_max; + + if (GetModuleFileName (NULL, path + 1, 510) == 0) + { + _tprintf (TEXT("Unable to install service - %s\n"), GetLastErrorText ()); + return 1; + } + + path[0] = TEXT('\"'); + _tcscat (path, TEXT("\"")); + + svc_ctl_mgr = OpenSCManager (NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE); + if (svc_ctl_mgr == NULL) + { + _tprintf (TEXT("OpenSCManager failed - %s\n"), GetLastErrorText ()); + return 1; + } - if ( ( dwCurrentState == SERVICE_RUNNING ) || - ( dwCurrentState == SERVICE_STOPPED ) ) - ssStatus.dwCheckPoint = 0; + for (i = 0; i < _service_max; i++) + { + service = CreateService (svc_ctl_mgr, + openvpn_service[i].name, + openvpn_service[i].display_name, + SERVICE_QUERY_STATUS, + SERVICE_WIN32_SHARE_PROCESS, + openvpn_service[i].start_type, + SERVICE_ERROR_NORMAL, + path, NULL, NULL, + openvpn_service[i].dependencies, + NULL, NULL); + if (service) + { + _tprintf (TEXT("%s installed.\n"), openvpn_service[i].display_name); + CloseServiceHandle (service); + --ret; + } else - ssStatus.dwCheckPoint = dwCheckPoint++; - + _tprintf (TEXT("CreateService failed - %s\n"), GetLastErrorText ()); + } - // Report the status of the service to the service control manager. - // - if (!(fResult = SetServiceStatus( sshStatusHandle, &ssStatus))) - { - AddToMessageLog(MSG_FLAGS_ERROR, TEXT("SetServiceStatus")); - } - } - return fResult; + CloseServiceHandle (svc_ctl_mgr); + return ret; } - -// -// FUNCTION: AddToMessageLog(LPTSTR lpszMsg) -// -// PURPOSE: Allows any thread to log an error message -// -// PARAMETERS: -// lpszMsg - text for message -// -// RETURN VALUE: -// none -// -// COMMENTS: -// -void AddToMessageLog(DWORD flags, LPTSTR lpszMsg) +static int +CmdStartService (openvpn_service_type type) { - TCHAR szMsg [(sizeof(SZSERVICENAME) / sizeof(TCHAR)) + 100 ]; - HANDLE hEventSource; - LPCSTR lpszStrings[2]; - - if ( !bDebug ) - { - if (flags & MSG_FLAGS_SYS_CODE) - dwErr = GetLastError(); - else - dwErr = 0; - - // Use event logging to log the error. - // - hEventSource = RegisterEventSource(NULL, TEXT(SZSERVICENAME)); - - _stprintf(szMsg, TEXT("%s error: %d"), TEXT(SZSERVICENAME), (int)dwErr); - lpszStrings[0] = szMsg; - lpszStrings[1] = lpszMsg; - - if (hEventSource != NULL) - { - ReportEvent(hEventSource, // handle of event source - // event type - (flags & MSG_FLAGS_ERROR) - ? EVENTLOG_ERROR_TYPE : EVENTLOG_INFORMATION_TYPE, - 0, // event category - 0, // event ID - NULL, // current user's SID - 2, // strings in lpszStrings - 0, // no bytes of raw data - lpszStrings, // array of error strings - NULL); // no raw data - - (VOID) DeregisterEventSource(hEventSource); - } - } -} - -void ResetError (void) -{ - dwErr = 0; -} + int ret = 1; + SC_HANDLE svc_ctl_mgr; + SC_HANDLE service; -/////////////////////////////////////////////////////////////////// -// -// The following code handles service installation and removal -// - - -// -// FUNCTION: CmdInstallService() -// -// PURPOSE: Installs the service -// -// PARAMETERS: -// none -// -// RETURN VALUE: -// 0 if success -// -// COMMENTS: -// -int CmdInstallService() -{ - SC_HANDLE schService; - SC_HANDLE schSCManager; - - TCHAR szPath[512]; - - int ret = 0; - - if ( GetModuleFileName( NULL, szPath+1, 510 ) == 0 ) - { - _tprintf(TEXT("Unable to install %s - %s\n"), TEXT(SZSERVICEDISPLAYNAME), GetLastErrorText(szErr, 256)); + svc_ctl_mgr = OpenSCManager (NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (svc_ctl_mgr == NULL) + { + _tprintf (TEXT("OpenSCManager failed - %s\n"), GetLastErrorText ()); return 1; - } - szPath[0] = '\"'; - strcat(szPath, "\""); - - schSCManager = OpenSCManager( - NULL, // machine (NULL == local) - NULL, // database (NULL == default) - SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE // access required - ); - if ( schSCManager ) - { - schService = CreateService( - schSCManager, // SCManager database - TEXT(SZSERVICENAME), // name of service - TEXT(SZSERVICEDISPLAYNAME), // name to display - SERVICE_QUERY_STATUS, // desired access - SERVICE_WIN32_OWN_PROCESS, // service type - SERVICE_DEMAND_START, // start type -- alternative: SERVICE_AUTO_START - SERVICE_ERROR_NORMAL, // error control type - szPath, // service's binary - NULL, // no load ordering group - NULL, // no tag identifier - TEXT(SZDEPENDENCIES), // dependencies - NULL, // LocalSystem account - NULL); // no password - - if ( schService ) - { - _tprintf(TEXT("%s installed.\n"), TEXT(SZSERVICEDISPLAYNAME) ); - CloseServiceHandle(schService); - } - else - { - _tprintf(TEXT("CreateService failed - %s\n"), GetLastErrorText(szErr, 256)); - ret = 1; - } - - CloseServiceHandle(schSCManager); - } - else - { - _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr,256)); - ret = 1; - } - return ret; -} - -// -// FUNCTION: CmdStartService() -// -// PURPOSE: Start the service -// -// PARAMETERS: -// none -// -// RETURN VALUE: -// 0 if success -// -// COMMENTS: - -int CmdStartService() -{ - int ret = 0; - - SC_HANDLE schSCManager; - SC_HANDLE schService; - - - // Open a handle to the SC Manager database. - schSCManager = OpenSCManager( - NULL, // local machine - NULL, // ServicesActive database - SC_MANAGER_ALL_ACCESS); // full access rights - - if (NULL == schSCManager) { - _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr,256)); - ret = 1; } - schService = OpenService( - schSCManager, // SCM database - SZSERVICENAME, // service name - SERVICE_ALL_ACCESS); + service = OpenService (svc_ctl_mgr, openvpn_service[type].name, SERVICE_ALL_ACCESS); + if (service) + { + if (StartService (service, 0, NULL)) + { + _tprintf (TEXT("Service Started\n")); + ret = 0; + } + else + _tprintf (TEXT("StartService failed - %s\n"), GetLastErrorText ()); - if (schService == NULL) { - _tprintf(TEXT("OpenService failed - %s\n"), GetLastErrorText(szErr,256)); - ret = 1; + CloseServiceHandle(service); } - - if (!StartService( - schService, // handle to service - 0, // number of arguments - NULL) ) // no arguments + else { - _tprintf(TEXT("StartService failed - %s\n"), GetLastErrorText(szErr,256)); - ret = 1; + _tprintf (TEXT("OpenService failed - %s\n"), GetLastErrorText ()); } - else - { - _tprintf(TEXT("Service Started\n")); - ret = 0; - } - CloseServiceHandle(schService); - CloseServiceHandle(schSCManager); - return ret; -} -// -// FUNCTION: CmdRemoveService() -// -// PURPOSE: Stops and removes the service -// -// PARAMETERS: -// none -// -// RETURN VALUE: -// 0 if success -// -// COMMENTS: -// -int CmdRemoveService() -{ - SC_HANDLE schService; - SC_HANDLE schSCManager; + CloseServiceHandle(svc_ctl_mgr); + return ret; +} - int ret = 0; - schSCManager = OpenSCManager( - NULL, // machine (NULL == local) - NULL, // database (NULL == default) - SC_MANAGER_CONNECT // access required - ); - if ( schSCManager ) - { - schService = OpenService(schSCManager, TEXT(SZSERVICENAME), DELETE | SERVICE_STOP | SERVICE_QUERY_STATUS); +static int +CmdRemoveServices () +{ + SC_HANDLE service; + SC_HANDLE svc_ctl_mgr; + SERVICE_STATUS status; + int i, ret = _service_max; - if (schService) - { - // try to stop the service - if ( ControlService( schService, SERVICE_CONTROL_STOP, &ssStatus ) ) - { - _tprintf(TEXT("Stopping %s."), TEXT(SZSERVICEDISPLAYNAME)); - Sleep( 1000 ); + svc_ctl_mgr = OpenSCManager (NULL, NULL, SC_MANAGER_CONNECT); + if (svc_ctl_mgr == NULL) + { + _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText ()); + return 1; + } - while ( QueryServiceStatus( schService, &ssStatus ) ) + for (i = 0; i < _service_max; i++) + { + openvpn_service_t *ovpn_svc = &openvpn_service[i]; + service = OpenService (svc_ctl_mgr, ovpn_svc->name, + DELETE | SERVICE_STOP | SERVICE_QUERY_STATUS); + if (service == NULL) + { + _tprintf (TEXT("OpenService failed - %s\n"), GetLastErrorText ()); + goto out; + } + + /* try to stop the service */ + if (ControlService (service, SERVICE_CONTROL_STOP, &status)) + { + _tprintf (TEXT("Stopping %s."), ovpn_svc->display_name); + Sleep (1000); + + while (QueryServiceStatus (service, &status)) { - if ( ssStatus.dwCurrentState == SERVICE_STOP_PENDING ) - { - _tprintf(TEXT(".")); - Sleep( 1000 ); - } - else - break; + if (status.dwCurrentState == SERVICE_STOP_PENDING) + { + _tprintf (TEXT(".")); + Sleep (1000); + } + else + break; } - if ( ssStatus.dwCurrentState == SERVICE_STOPPED ) - _tprintf(TEXT("\n%s stopped.\n"), TEXT(SZSERVICEDISPLAYNAME) ); - else - { - _tprintf(TEXT("\n%s failed to stop.\n"), TEXT(SZSERVICEDISPLAYNAME) ); - ret = 1; - } - - } - - // now remove the service - if ( DeleteService(schService) ) - _tprintf(TEXT("%s removed.\n"), TEXT(SZSERVICEDISPLAYNAME) ); - else - { - _tprintf(TEXT("DeleteService failed - %s\n"), GetLastErrorText(szErr,256)); - ret = 1; - } - - - CloseServiceHandle(schService); - } + if (status.dwCurrentState == SERVICE_STOPPED) + _tprintf (TEXT("\n%s stopped.\n"), ovpn_svc->display_name); + else + _tprintf (TEXT("\n%s failed to stop.\n"), ovpn_svc->display_name); + } + + /* now remove the service */ + if (DeleteService (service)) + { + _tprintf (TEXT("%s removed.\n"), ovpn_svc->display_name); + --ret; + } else - { - _tprintf(TEXT("OpenService failed - %s\n"), GetLastErrorText(szErr,256)); - ret = 1; - } - - CloseServiceHandle(schSCManager); - } - else - { - _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr,256)); - ret = 1; - } - return ret; -} - - - - -/////////////////////////////////////////////////////////////////// -// -// The following code is for running the service as a console app -// - - -// -// FUNCTION: CmdDebugService(int argc, char ** argv) -// -// PURPOSE: Runs the service as a console application -// -// PARAMETERS: -// argc - number of command line arguments -// argv - array of command line arguments -// -// RETURN VALUE: -// none -// -// COMMENTS: -// -void CmdDebugService(int argc, char ** argv) -{ - DWORD dwArgc; - LPTSTR *lpszArgv; - -#ifdef UNICODE - lpszArgv = CommandLineToArgvW(GetCommandLineW(), &(dwArgc) ); - if (NULL == lpszArgv) - { - // CommandLineToArvW failed!! - _tprintf(TEXT("CmdDebugService CommandLineToArgvW returned NULL\n")); - return; - } -#else - dwArgc = (DWORD) argc; - lpszArgv = argv; -#endif - - _tprintf(TEXT("Debugging %s.\n"), TEXT(SZSERVICEDISPLAYNAME)); - - SetConsoleCtrlHandler( ControlHandler, TRUE ); + _tprintf (TEXT("DeleteService failed - %s\n"), GetLastErrorText ()); - ServiceStart( dwArgc, lpszArgv ); - -#ifdef UNICODE -// Must free memory allocated for arguments - - GlobalFree(lpszArgv); -#endif // UNICODE + CloseServiceHandle (service); + } +out: + CloseServiceHandle (svc_ctl_mgr); + return ret; } -// -// FUNCTION: ControlHandler ( DWORD dwCtrlType ) -// -// PURPOSE: Handled console control events -// -// PARAMETERS: -// dwCtrlType - type of control event -// -// RETURN VALUE: -// True - handled -// False - unhandled -// -// COMMENTS: -// -BOOL WINAPI ControlHandler ( DWORD dwCtrlType ) +int +_tmain (int argc, TCHAR *argv[]) { - switch ( dwCtrlType ) - { - case CTRL_BREAK_EVENT: // use Ctrl+C or Ctrl+Break to simulate - case CTRL_C_EVENT: // SERVICE_CONTROL_STOP in debug mode - _tprintf(TEXT("Stopping %s.\n"), TEXT(SZSERVICEDISPLAYNAME)); - ServiceStop(); - return TRUE; - break; - - } - return FALSE; -} - -// -// FUNCTION: GetLastErrorText -// -// PURPOSE: copies error message text to string -// -// PARAMETERS: -// lpszBuf - destination buffer -// dwSize - size of buffer -// -// RETURN VALUE: -// destination buffer -// -// COMMENTS: -// -LPTSTR GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize ) -{ - DWORD dwRet; - LPTSTR lpszTemp = NULL; - - dwRet = FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_ARGUMENT_ARRAY, - NULL, - GetLastError(), - LANG_NEUTRAL, - (LPTSTR)&lpszTemp, - 0, - NULL ); - - // supplied buffer is not long enough - if ( !dwRet || ( (long)dwSize < (long)dwRet+14 ) ) - lpszBuf[0] = TEXT('\0'); - else - { - lpszTemp[lstrlen(lpszTemp)-2] = TEXT('\0'); //remove cr and newline character - _stprintf( lpszBuf, TEXT("%s (0x%x)"), lpszTemp, (int)GetLastError() ); - } - - if ( lpszTemp ) - LocalFree((HLOCAL) lpszTemp ); - - return lpszBuf; + SERVICE_TABLE_ENTRY dispatchTable[] = { + { automatic_service.name, ServiceStartAutomatic }, + { interactive_service.name, ServiceStartInteractive }, + { NULL, NULL } + }; + + openvpn_service[0] = automatic_service; + openvpn_service[1] = interactive_service; + + if (argc > 1 && (*argv[1] == TEXT('-') || *argv[1] == TEXT('/'))) + { + if (_tcsicmp (TEXT("install"), argv[1] + 1) == 0) + return CmdInstallServices (); + else if (_tcsicmp (TEXT("remove"), argv[1] + 1) == 0) + return CmdRemoveServices (); + else if (_tcsicmp (TEXT("start"), argv[1] + 1) == 0) + { + BOOL is_auto = argc < 3 || _tcsicmp (TEXT("interactive"), argv[2]) != 0; + return CmdStartService (is_auto ? automatic : interactive); + } + else + goto dispatch; + + return 0; + } + + /* If it doesn't match any of the above parameters + * the service control manager may be starting the service + * so we must call StartServiceCtrlDispatcher + */ +dispatch: + _tprintf (TEXT("%s -install to install the services\n"), APPNAME); + _tprintf (TEXT("%s -start <name> to start a service (\"automatic\" or \"interactive\")\n"), APPNAME); + _tprintf (TEXT("%s -remove to remove the services\n"), APPNAME); + _tprintf (TEXT("\nStartServiceCtrlDispatcher being called.\n")); + _tprintf (TEXT("This may take several seconds. Please wait.\n")); + + if (!StartServiceCtrlDispatcher (dispatchTable)) + MsgToEventLog (MSG_FLAGS_ERROR, TEXT("StartServiceCtrlDispatcher failed.")); + + return 0; } diff --git a/src/openvpnserv/service.h b/src/openvpnserv/service.h index e89a89f..94bfb07 100644 --- a/src/openvpnserv/service.h +++ b/src/openvpnserv/service.h @@ -1,139 +1,92 @@ -/*--------------------------------------------------------------------------- -THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF -ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A -PARTICULAR PURPOSE. - -Copyright (C) 1993 - 2000. Microsoft Corporation. All rights reserved. - - MODULE: service.h - - Comments: The use of this header file and the accompanying service.c - file simplifies the process of writting a service. You as a developer - simply need to follow the TODO's outlined in this header file, and - implement the ServiceStart() and ServiceStop() functions. - - There is no need to modify the code in service.c. Just add service.c - to your project and link with the following libraries... - - libcmt.lib kernel32.lib advapi.lib shell32.lib - - This code also supports unicode. Be sure to compile both service.c and - and code #include "service.h" with the same Unicode setting. - - Upon completion, your code will have the following command line interface - - <service exe> -? to display this list - <service exe> -install to install the service - <service exe> -remove to remove the service - <service exe> -debug <params> to run as a console app for debugging - - Note: This code also implements Ctrl+C and Ctrl+Break handlers - when using the debug option. These console events cause - your ServiceStop routine to be called - - Also, this code only handles the OWN_SERVICE service type - running in the LOCAL_SYSTEM security context. - - To control your service ( start, stop, etc ) you may use the - Services control panel applet or the NET.EXE program. - - To aid in writing/debugging service, the - SDK contains a utility (MSTOOLS\BIN\SC.EXE) that - can be used to control, configure, or obtain service status. - SC displays complete status for any service/driver - in the service database, and allows any of the configuration - parameters to be easily changed at the command line. - For more information on SC.EXE, type SC at the command line. - - -------------------------------------------------------------------------------*/ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2013 Heiko Hund <heiko.hund@sophos.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ #ifndef _SERVICE_H #define _SERVICE_H - -#ifdef __cplusplus -extern "C" { +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" #endif -////////////////////////////////////////////////////////////////////////////// -//// todo: change to desired strings -//// -// name of the executable -#define SZAPPNAME PACKAGE "serv" -// internal name of the service -#define SZSERVICENAME PACKAGE_NAME "Service" -// displayed name of the service -#define SZSERVICEDISPLAYNAME PACKAGE_NAME " Service" -// list of service dependencies - "dep1\0dep2\0\0" -#define SZDEPENDENCIES TAP_WIN_COMPONENT_ID "\0Dhcp\0\0" -////////////////////////////////////////////////////////////////////////////// - - - -////////////////////////////////////////////////////////////////////////////// -//// todo: ServiceStart()must be defined by in your code. -//// The service should use ReportStatusToSCMgr to indicate -//// progress. This routine must also be used by StartService() -//// to report to the SCM when the service is running. -//// -//// If a ServiceStop procedure is going to take longer than -//// 3 seconds to execute, it should spawn a thread to -//// execute the stop code, and return. Otherwise, the -//// ServiceControlManager will believe that the service has -//// stopped responding -//// - VOID ServiceStart(DWORD dwArgc, LPTSTR *lpszArgv); - VOID ServiceStop(); -////////////////////////////////////////////////////////////////////////////// - - - -////////////////////////////////////////////////////////////////////////////// -//// The following are procedures which -//// may be useful to call within the above procedures, -//// but require no implementation by the user. -//// They are implemented in service.c - -// -// FUNCTION: ReportStatusToSCMgr() -// -// PURPOSE: Sets the current status of the service and -// reports it to the Service Control Manager -// -// PARAMETERS: -// dwCurrentState - the state of the service -// dwWin32ExitCode - error code to report -// dwWaitHint - worst case estimate to next checkpoint -// -// RETURN VALUE: -// TRUE - success -// FALSE - failure -// - BOOL ReportStatusToSCMgr(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint); - - -// -// FUNCTION: AddToMessageLog(LPTSTR lpszMsg) -// -// PURPOSE: Allows any thread to log an error message -// -// PARAMETERS: -// lpszMsg - text for message -// -// RETURN VALUE: -// none -// -# define MSG_FLAGS_ERROR (1<<0) -# define MSG_FLAGS_SYS_CODE (1<<1) - void AddToMessageLog(DWORD flags, LPTSTR lpszMsg); - void ResetError (void); -////////////////////////////////////////////////////////////////////////////// - - -#ifdef __cplusplus -} -#endif +#include <windows.h> +#include <stdlib.h> +#include <tchar.h> + +#define APPNAME TEXT(PACKAGE "serv") +#define SERVICE_DEPENDENCIES TAP_WIN_COMPONENT_ID "\0Dhcp\0\0" + +/* + * Message handling + */ +#define MSG_FLAGS_ERROR (1<<0) +#define MSG_FLAGS_SYS_CODE (1<<1) +#define M_INFO (0) /* informational */ +#define M_SYSERR (MSG_FLAGS_ERROR|MSG_FLAGS_SYS_CODE) /* error + system code */ +#define M_ERR (MSG_FLAGS_ERROR) /* error */ + +typedef enum { + automatic, + interactive, + _service_max +} openvpn_service_type; + +typedef struct { + openvpn_service_type type; + TCHAR *name; + TCHAR *display_name; + TCHAR *dependencies; + DWORD start_type; +} openvpn_service_t; + +#define MAX_NAME 256 +typedef struct { + TCHAR exe_path[MAX_PATH]; + TCHAR config_dir[MAX_PATH]; + TCHAR ext_string[16]; + TCHAR log_dir[MAX_PATH]; + TCHAR ovpn_admin_group[MAX_NAME]; + DWORD priority; + BOOL append; +} settings_t; + +extern openvpn_service_t automatic_service; +extern openvpn_service_t interactive_service; + + +VOID WINAPI ServiceStartAutomatic (DWORD argc, LPTSTR *argv); +VOID WINAPI ServiceStartInteractive (DWORD argc, LPTSTR *argv); + +int openvpn_vsntprintf (LPTSTR str, size_t size, LPCTSTR format, va_list arglist); +int openvpn_sntprintf (LPTSTR str, size_t size, LPCTSTR format, ...); + +DWORD GetOpenvpnSettings (settings_t *s); + +BOOL ReportStatusToSCMgr (SERVICE_STATUS_HANDLE service, SERVICE_STATUS *status); + +LPCTSTR GetLastErrorText (); +DWORD MsgToEventLog (DWORD flags, LPCTSTR lpszMsg, ...); #endif diff --git a/src/openvpnserv/validate.c b/src/openvpnserv/validate.c new file mode 100644 index 0000000..7458d75 --- /dev/null +++ b/src/openvpnserv/validate.c @@ -0,0 +1,249 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2016 Selva Nair <selva.nair@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "validate.h" + +#include <lmaccess.h> +#include <shlwapi.h> +#include <lm.h> + +static const WCHAR *white_list[] = + { + L"auth-retry", + L"config", + L"log", + L"log-append", + L"management", + L"management-forget-disconnect", + L"management-hold", + L"management-query-passwords", + L"management-query-proxy", + L"management-signal", + L"management-up-down", + L"mute", + L"setenv", + L"service", + L"verb", + + NULL /* last value */ + }; + +/* + * Check workdir\fname is inside config_dir + * The logic here is simple: we may reject some valid paths if ..\ is in any of the strings + */ +static BOOL +CheckConfigPath (const WCHAR *workdir, const WCHAR *fname, const settings_t *s) +{ + WCHAR tmp[MAX_PATH]; + const WCHAR *config_file = NULL; + const WCHAR *config_dir = NULL; + + /* convert fname to full path */ + if (PathIsRelativeW (fname) ) + { + snwprintf (tmp, _countof(tmp), L"%s\\%s", workdir, fname); + tmp[_countof(tmp)-1] = L'\0'; + config_file = tmp; + } + else + { + config_file = fname; + } + +#ifdef UNICODE + config_dir = s->config_dir; +#else + if (MultiByteToWideChar (CP_UTF8, 0, s->config_dir, -1, widepath, MAX_PATH) == 0) + { + MsgToEventLog (M_SYSERR, TEXT("Failed to convert config_dir name to WideChar")); + return FALSE; + } + config_dir = widepath; +#endif + + if (wcsncmp (config_dir, config_file, wcslen(config_dir)) == 0 && + wcsstr (config_file + wcslen(config_dir), L"..") == NULL ) + return TRUE; + + return FALSE; +} + + +/* + * A simple linear search meant for a small wchar_t *array. + * Returns index to the item if found, -1 otherwise. + */ +static int +OptionLookup (const WCHAR *name, const WCHAR *white_list[]) +{ + int i; + + for (i = 0 ; white_list[i]; i++) + { + if ( wcscmp(white_list[i], name) == 0 ) + return i; + } + + return -1; +} + +/* + * The Administrators group may be localized or renamed by admins. + * Get the local name of the group using the SID. + */ +static BOOL +GetBuiltinAdminGroupName (WCHAR *name, DWORD nlen) +{ + BOOL b = FALSE; + PSID admin_sid = NULL; + DWORD sid_size = SECURITY_MAX_SID_SIZE; + SID_NAME_USE snu; + + WCHAR domain[MAX_NAME]; + DWORD dlen = _countof(domain); + + admin_sid = malloc(sid_size); + if (!admin_sid) + return FALSE; + + b = CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, admin_sid, &sid_size); + if(b) + { + b = LookupAccountSidW(NULL, admin_sid, name, &nlen, domain, &dlen, &snu); + } + + free (admin_sid); + + return b; +} + +/* + * Check whether user is a member of Administrators group or + * the group specified in s->ovpn_admin_group + */ +BOOL +IsAuthorizedUser (SID *sid, settings_t *s) +{ + LOCALGROUP_USERS_INFO_0 *groups = NULL; + DWORD nread; + DWORD nmax; + WCHAR *tmp = NULL; + const WCHAR *admin_group[2]; + WCHAR username[MAX_NAME]; + WCHAR domain[MAX_NAME]; + WCHAR sysadmin_group[MAX_NAME]; + DWORD err, len = MAX_NAME; + int i; + BOOL ret = FALSE; + SID_NAME_USE sid_type; + + /* Get username */ + if (!LookupAccountSidW (NULL, sid, username, &len, domain, &len, &sid_type)) + { + MsgToEventLog (M_SYSERR, TEXT("LookupAccountSid")); + goto out; + } + + /* Get an array of groups the user is member of */ + err = NetUserGetLocalGroups (NULL, username, 0, LG_INCLUDE_INDIRECT, (LPBYTE *) &groups, + MAX_PREFERRED_LENGTH, &nread, &nmax); + if (err && err != ERROR_MORE_DATA) + { + SetLastError (err); + MsgToEventLog (M_SYSERR, TEXT("NetUserGetLocalGroups")); + goto out; + } + + if (GetBuiltinAdminGroupName(sysadmin_group, _countof(sysadmin_group))) + { + admin_group[0] = sysadmin_group; + } + else + { + MsgToEventLog (M_SYSERR, TEXT("Failed to get the name of Administrators group. Using the default.")); + /* use the default value */ + admin_group[0] = SYSTEM_ADMIN_GROUP; + } + +#ifdef UNICODE + admin_group[1] = s->ovpn_admin_group; +#else + tmp = NULL; + len = MultiByteToWideChar (CP_UTF8, 0, s->ovpn_admin_group, -1, NULL, 0); + if (len == 0 || (tmp = malloc (len*sizeof(WCHAR))) == NULL) + { + MsgToEventLog (M_SYSERR, TEXT("Failed to convert admin group name to WideChar")); + goto out; + } + MultiByteToWideChar (CP_UTF8, 0, s->ovpn_admin_group, -1, tmp, len); + admin_group[1] = tmp; +#endif + + /* Check if user's groups include any of the admin groups */ + for (i = 0; i < nread; i++) + { + if ( wcscmp (groups[i].lgrui0_name, admin_group[0]) == 0 || + wcscmp (groups[i].lgrui0_name, admin_group[1]) == 0 + ) + { + MsgToEventLog (M_INFO, TEXT("Authorizing user %s by virtue of membership in group %s"), + username, groups[i].lgrui0_name); + ret = TRUE; + break; + } + } + +out: + if (groups) + NetApiBufferFree (groups); + free (tmp); + + return ret; +} + +/* + * Check whether option argv[0] is white-listed. If argv[0] == "--config", + * also check that argv[1], if present, passes CheckConfigPath(). + * The caller should set argc to the number of valid elements in argv[] array. + */ +BOOL +CheckOption (const WCHAR *workdir, int argc, WCHAR *argv[], const settings_t *s) +{ + /* Do not modify argv or *argv -- ideally it should be const WCHAR *const *, but alas...*/ + + if ( wcscmp (argv[0], L"--config") == 0 && + argc > 1 && + !CheckConfigPath (workdir, argv[1], s) + ) + { + return FALSE; + } + + /* option name starts at 2 characters from argv[i] */ + if (OptionLookup (argv[0] + 2, white_list) == -1) /* not found */ + return FALSE; + + return TRUE; +} diff --git a/src/openvpnserv/validate.h b/src/openvpnserv/validate.h new file mode 100644 index 0000000..0d0270a --- /dev/null +++ b/src/openvpnserv/validate.h @@ -0,0 +1,48 @@ + +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2016 Selva Nair <selva.nair@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef VALIDATE_H +#define VALIDATE_H + +#include "service.h" + +/* Authorized groups who can use any options and config locations */ +#define SYSTEM_ADMIN_GROUP TEXT("Administrators") +#define OVPN_ADMIN_GROUP TEXT("OpenVPN Administrators") +/* The last one may be reset in registry: HKLM\Software\OpenVPN\ovpn_admin_group */ + +BOOL +IsAuthorizedUser (SID *sid, settings_t *s); + +BOOL +CheckOption (const WCHAR *workdir, int narg, WCHAR *argv[], const settings_t *s); + +static inline BOOL +IsOption (const WCHAR *o) +{ + return (wcsncmp (o, L"--", 2) == 0); +} + +#endif |