Update networking layer w/ CURL and emscripten impl

This commit is contained in:
2025-11-08 01:50:36 +11:00
parent a17925904d
commit f6874dc55a
4105 changed files with 694617 additions and 179 deletions
+18
View File
@@ -0,0 +1,18 @@
# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
#
# SPDX-License-Identifier: curl
allowfunc accept
allowfunc fclose
allowfunc fopen
allowfunc fprintf
allowfunc freeaddrinfo
allowfunc getaddrinfo
allowfunc open
allowfunc printf
allowfunc recv
allowfunc send
allowfunc snprintf
allowfunc socket
allowfunc sscanf
allowfunc vsnprintf
+47
View File
@@ -0,0 +1,47 @@
#***************************************************************************
# _ _ ____ _
# Project ___| | | | _ \| |
# / __| | | | |_) | |
# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://curl.se/docs/copyright.html.
#
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
# copies of the Software, and permit persons to whom the Software is
# furnished to do so, under the terms of the COPYING file.
#
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
# KIND, either express or implied.
#
# SPDX-License-Identifier: curl
#
###########################################################################
# Get BUNDLE, FIRST_C, FIRST_H, UTILS_C, UTILS_H, CURLX_C, TESTS_C variables
curl_transform_makefile_inc("Makefile.inc" "${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake")
include("${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake")
add_custom_command(OUTPUT "${BUNDLE}.c"
COMMAND ${PERL_EXECUTABLE} "${PROJECT_SOURCE_DIR}/scripts/mk-unity.pl"
--include ${UTILS_C} ${CURLX_C} --test ${TESTS_C} > "${BUNDLE}.c"
DEPENDS
"${PROJECT_SOURCE_DIR}/scripts/mk-unity.pl" "${CMAKE_CURRENT_SOURCE_DIR}/Makefile.inc"
${FIRST_C} ${UTILS_C} ${CURLX_C} ${TESTS_C}
VERBATIM)
add_executable(${BUNDLE} EXCLUDE_FROM_ALL "${BUNDLE}.c")
add_dependencies(testdeps ${BUNDLE})
target_link_libraries(${BUNDLE} ${CURL_NETWORK_AND_TIME_LIBS})
target_include_directories(${BUNDLE} PRIVATE
"${PROJECT_BINARY_DIR}/lib" # for "curl_config.h"
"${PROJECT_SOURCE_DIR}/lib" # for "curl_setup.h", curlx
"${CMAKE_CURRENT_SOURCE_DIR}" # for the generated bundle source to find included test sources
)
set_target_properties(${BUNDLE} PROPERTIES OUTPUT_NAME "${BUNDLE}" PROJECT_LABEL "Test ${BUNDLE}" UNITY_BUILD OFF C_CLANG_TIDY "")
curl_add_clang_tidy_test_target("${BUNDLE}-clang-tidy" ${BUNDLE} ${FIRST_C} ${UTILS_C} ${TESTS_C})
+74
View File
@@ -0,0 +1,74 @@
#***************************************************************************
# _ _ ____ _
# Project ___| | | | _ \| |
# / __| | | | |_) | |
# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://curl.se/docs/copyright.html.
#
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
# copies of the Software, and permit persons to whom the Software is
# furnished to do so, under the terms of the COPYING file.
#
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
# KIND, either express or implied.
#
# SPDX-License-Identifier: curl
#
###########################################################################
AUTOMAKE_OPTIONS = foreign nostdinc
# Specify our include paths here, and do it relative to $(top_srcdir) and
# $(top_builddir), to ensure that these paths which belong to the library
# being currently built and tested are searched before the library which
# might possibly already be installed in the system.
#
# $(top_srcdir)/include is for libcurl's external include files
# $(top_builddir)/lib is for libcurl's generated lib/curl_config.h file
# $(top_srcdir)/lib for libcurl's lib/curl_setup.h and other "borrowed" files
# $(srcdir) for the generated bundle source to find included test sources
AM_CPPFLAGS = -I$(top_srcdir)/include \
-I$(top_builddir)/lib \
-I$(top_srcdir)/lib \
-I$(srcdir)
# Get BUNDLE, FIRST_C, FIRST_H, UTILS_C, UTILS_H, CURLX_C, TESTS_C variables
include Makefile.inc
EXTRA_DIST = CMakeLists.txt .checksrc $(FIRST_C) $(FIRST_H) $(UTILS_C) $(UTILS_H) $(TESTS_C)
CFLAGS += @CURL_CFLAG_EXTRAS@
# Prevent LIBS from being used for all link targets
LIBS = $(BLANK_AT_MAKETIME)
$(BUNDLE).c: $(top_srcdir)/scripts/mk-unity.pl Makefile.inc $(FIRST_C) $(UTILS_C) $(CURLX_C) $(TESTS_C)
@PERL@ $(top_srcdir)/scripts/mk-unity.pl --include $(UTILS_C) $(CURLX_C) --test $(TESTS_C) > $(BUNDLE).c
noinst_PROGRAMS = $(BUNDLE)
LDADD = @CURL_NETWORK_AND_TIME_LIBS@
CLEANFILES = $(BUNDLE).c
CHECKSRC = $(CS_$(V))
CS_0 = @echo " RUN " $@;
CS_1 =
CS_ = $(CS_0)
# ignore generated C files since they play by slightly different rules!
checksrc:
$(CHECKSRC)(@PERL@ $(top_srcdir)/scripts/checksrc.pl -D$(srcdir) \
-W$(srcdir)/$(BUNDLE).c \
$(srcdir)/*.[ch])
if NOT_CURL_CI
all-local: checksrc
endif
clean-local:
rm -f $(BUNDLE)
+792
View File
@@ -0,0 +1,792 @@
# Makefile.in generated by automake 1.16.5 from Makefile.am.
# @configure_input@
# Copyright (C) 1994-2021 Free Software Foundation, Inc.
# This Makefile.in is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.
@SET_MAKE@
#***************************************************************************
# _ _ ____ _
# Project ___| | | | _ \| |
# / __| | | | |_) | |
# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://curl.se/docs/copyright.html.
#
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
# copies of the Software, and permit persons to whom the Software is
# furnished to do so, under the terms of the COPYING file.
#
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
# KIND, either express or implied.
#
# SPDX-License-Identifier: curl
#
###########################################################################
# Shared between CMakeLists.txt and Makefile.am
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__make_running_with_option = \
case $${target_option-} in \
?) ;; \
*) echo "am__make_running_with_option: internal error: invalid" \
"target option '$${target_option-}' specified" >&2; \
exit 1;; \
esac; \
has_opt=no; \
sane_makeflags=$$MAKEFLAGS; \
if $(am__is_gnu_make); then \
sane_makeflags=$$MFLAGS; \
else \
case $$MAKEFLAGS in \
*\\[\ \ ]*) \
bs=\\; \
sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
| sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
esac; \
fi; \
skip_next=no; \
strip_trailopt () \
{ \
flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
}; \
for flg in $$sane_makeflags; do \
test $$skip_next = yes && { skip_next=no; continue; }; \
case $$flg in \
*=*|--*) continue;; \
-*I) strip_trailopt 'I'; skip_next=yes;; \
-*I?*) strip_trailopt 'I';; \
-*O) strip_trailopt 'O'; skip_next=yes;; \
-*O?*) strip_trailopt 'O';; \
-*l) strip_trailopt 'l'; skip_next=yes;; \
-*l?*) strip_trailopt 'l';; \
-[dEDm]) skip_next=yes;; \
-[JT]) skip_next=yes;; \
esac; \
case $$flg in \
*$$target_option*) has_opt=yes; break;; \
esac; \
done; \
test $$has_opt = yes
am__make_dryrun = (target_option=n; $(am__make_running_with_option))
am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
pkgdatadir = $(datadir)/@PACKAGE@
pkgincludedir = $(includedir)/@PACKAGE@
pkglibdir = $(libdir)/@PACKAGE@
pkglibexecdir = $(libexecdir)/@PACKAGE@
am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
install_sh_DATA = $(install_sh) -c -m 644
install_sh_PROGRAM = $(install_sh) -c
install_sh_SCRIPT = $(install_sh) -c
INSTALL_HEADER = $(INSTALL_DATA)
transform = $(program_transform_name)
NORMAL_INSTALL = :
PRE_INSTALL = :
POST_INSTALL = :
NORMAL_UNINSTALL = :
PRE_UNINSTALL = :
POST_UNINSTALL = :
build_triplet = @build@
host_triplet = @host@
noinst_PROGRAMS = $(am__EXEEXT_1)
subdir = tests/server
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/m4/curl-amissl.m4 \
$(top_srcdir)/m4/curl-apple-sectrust.m4 \
$(top_srcdir)/m4/curl-compilers.m4 \
$(top_srcdir)/m4/curl-confopts.m4 \
$(top_srcdir)/m4/curl-functions.m4 \
$(top_srcdir)/m4/curl-gnutls.m4 \
$(top_srcdir)/m4/curl-mbedtls.m4 \
$(top_srcdir)/m4/curl-openssl.m4 \
$(top_srcdir)/m4/curl-override.m4 \
$(top_srcdir)/m4/curl-reentrant.m4 \
$(top_srcdir)/m4/curl-rustls.m4 \
$(top_srcdir)/m4/curl-schannel.m4 \
$(top_srcdir)/m4/curl-sysconfig.m4 \
$(top_srcdir)/m4/curl-wolfssl.m4 $(top_srcdir)/m4/libtool.m4 \
$(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
$(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
$(top_srcdir)/m4/xc-am-iface.m4 \
$(top_srcdir)/m4/xc-cc-check.m4 \
$(top_srcdir)/m4/xc-lt-iface.m4 \
$(top_srcdir)/m4/xc-val-flgs.m4 \
$(top_srcdir)/m4/zz40-xc-ovr.m4 \
$(top_srcdir)/m4/zz50-xc-ovr.m4 \
$(top_srcdir)/m4/zz60-xc-ovr.m4 $(top_srcdir)/acinclude.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)/lib/curl_config.h
CONFIG_CLEAN_FILES =
CONFIG_CLEAN_VPATH_FILES =
am__EXEEXT_1 = servers$(EXEEXT)
PROGRAMS = $(noinst_PROGRAMS)
servers_SOURCES = servers.c
servers_OBJECTS = servers.$(OBJEXT)
servers_LDADD = $(LDADD)
servers_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 =
AM_V_P = $(am__v_P_@AM_V@)
am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
am__v_P_0 = false
am__v_P_1 = :
AM_V_GEN = $(am__v_GEN_@AM_V@)
am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
am__v_GEN_0 = @echo " GEN " $@;
am__v_GEN_1 =
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 =
depcomp = $(SHELL) $(top_srcdir)/depcomp
am__maybe_remake_depfiles = depfiles
am__depfiles_remade = ./$(DEPDIR)/servers.Po
am__mv = mv -f
COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
$(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
$(AM_CFLAGS) $(CFLAGS)
AM_V_CC = $(am__v_CC_@AM_V@)
am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
am__v_CC_0 = @echo " CC " $@;
am__v_CC_1 =
CCLD = $(CC)
LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
$(AM_LDFLAGS) $(LDFLAGS) -o $@
AM_V_CCLD = $(am__v_CCLD_@AM_V@)
am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
am__v_CCLD_0 = @echo " CCLD " $@;
am__v_CCLD_1 =
SOURCES = servers.c
DIST_SOURCES = servers.c
am__can_run_installinfo = \
case $$AM_UPDATE_INFO_DIR in \
n|no|NO) false;; \
*) (install-info --version) >/dev/null 2>&1;; \
esac
am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
# Read a list of newline-separated strings from the standard input,
# and print each of them once, without duplicates. Input order is
# *not* preserved.
am__uniquify_input = $(AWK) '\
BEGIN { nonempty = 0; } \
{ items[$$0] = 1; nonempty = 1; } \
END { if (nonempty) { for (i in items) print i; }; } \
'
# Make sure the list of sources is unique. This is necessary because,
# e.g., the same source file might be shared among _SOURCES variables
# for different programs/libraries.
am__define_uniq_tagged_files = \
list='$(am__tagged_files)'; \
unique=`for i in $$list; do \
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
done | $(am__uniquify_input)`
am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.inc \
$(top_srcdir)/depcomp
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
ACLOCAL = @ACLOCAL@
AMTAR = @AMTAR@
AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
APXS = @APXS@
AR = @AR@
AR_FLAGS = @AR_FLAGS@
AS = @AS@
AUTOCONF = @AUTOCONF@
AUTOHEADER = @AUTOHEADER@
AUTOMAKE = @AUTOMAKE@
AWK = @AWK@
BLANK_AT_MAKETIME = @BLANK_AT_MAKETIME@
CADDY = @CADDY@
CC = @CC@
CCDEPMODE = @CCDEPMODE@
CFLAGS = @CFLAGS@ @CURL_CFLAG_EXTRAS@
CFLAG_CURL_SYMBOL_HIDING = @CFLAG_CURL_SYMBOL_HIDING@
CONFIGURE_OPTIONS = @CONFIGURE_OPTIONS@
CPP = @CPP@
CPPFLAGS = @CPPFLAGS@
CSCOPE = @CSCOPE@
CTAGS = @CTAGS@
CURLVERSION = @CURLVERSION@
CURL_CA_BUNDLE = @CURL_CA_BUNDLE@
CURL_CA_EMBED = @CURL_CA_EMBED@
CURL_CFLAG_EXTRAS = @CURL_CFLAG_EXTRAS@
CURL_CPP = @CURL_CPP@
CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX = @CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX@
CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME = @CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME@
CURL_NETWORK_AND_TIME_LIBS = @CURL_NETWORK_AND_TIME_LIBS@
CYGPATH_W = @CYGPATH_W@
DANTED = @DANTED@
DEFS = @DEFS@
DEPDIR = @DEPDIR@
DLLTOOL = @DLLTOOL@
DSYMUTIL = @DSYMUTIL@
DUMPBIN = @DUMPBIN@
ECHO_C = @ECHO_C@
ECHO_N = @ECHO_N@
ECHO_T = @ECHO_T@
EGREP = @EGREP@
ENABLE_SHARED = @ENABLE_SHARED@
ENABLE_STATIC = @ENABLE_STATIC@
ETAGS = @ETAGS@
EXEEXT = @EXEEXT@
FGREP = @FGREP@
FILECMD = @FILECMD@
FISH_FUNCTIONS_DIR = @FISH_FUNCTIONS_DIR@
GCOV = @GCOV@
GREP = @GREP@
HAVE_LIBZ = @HAVE_LIBZ@
HTTPD = @HTTPD@
HTTPD_NGHTTPX = @HTTPD_NGHTTPX@
INSTALL = @INSTALL@
INSTALL_DATA = @INSTALL_DATA@
INSTALL_PROGRAM = @INSTALL_PROGRAM@
INSTALL_SCRIPT = @INSTALL_SCRIPT@
INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
LCOV = @LCOV@
LD = @LD@
LDFLAGS = @LDFLAGS@
LIBCURL_PC_CFLAGS = @LIBCURL_PC_CFLAGS@
LIBCURL_PC_CFLAGS_PRIVATE = @LIBCURL_PC_CFLAGS_PRIVATE@
LIBCURL_PC_LDFLAGS_PRIVATE = @LIBCURL_PC_LDFLAGS_PRIVATE@
LIBCURL_PC_LIBS = @LIBCURL_PC_LIBS@
LIBCURL_PC_LIBS_PRIVATE = @LIBCURL_PC_LIBS_PRIVATE@
LIBCURL_PC_REQUIRES = @LIBCURL_PC_REQUIRES@
LIBCURL_PC_REQUIRES_PRIVATE = @LIBCURL_PC_REQUIRES_PRIVATE@
LIBOBJS = @LIBOBJS@
# Prevent LIBS from being used for all link targets
LIBS = $(BLANK_AT_MAKETIME)
LIBTOOL = @LIBTOOL@
LIPO = @LIPO@
LN_S = @LN_S@
LTLIBOBJS = @LTLIBOBJS@
LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
MAINT = @MAINT@
MAKEINFO = @MAKEINFO@
MANIFEST_TOOL = @MANIFEST_TOOL@
MKDIR_P = @MKDIR_P@
NM = @NM@
NMEDIT = @NMEDIT@
OBJDUMP = @OBJDUMP@
OBJEXT = @OBJEXT@
OTOOL = @OTOOL@
OTOOL64 = @OTOOL64@
PACKAGE = @PACKAGE@
PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
PACKAGE_NAME = @PACKAGE_NAME@
PACKAGE_STRING = @PACKAGE_STRING@
PACKAGE_TARNAME = @PACKAGE_TARNAME@
PACKAGE_URL = @PACKAGE_URL@
PACKAGE_VERSION = @PACKAGE_VERSION@
PATH_SEPARATOR = @PATH_SEPARATOR@
PERL = @PERL@
PKGCONFIG = @PKGCONFIG@
RANLIB = @RANLIB@
RC = @RC@
SED = @SED@
SET_MAKE = @SET_MAKE@
SHELL = @SHELL@
SSL_BACKENDS = @SSL_BACKENDS@
STRIP = @STRIP@
SUPPORT_FEATURES = @SUPPORT_FEATURES@
SUPPORT_PROTOCOLS = @SUPPORT_PROTOCOLS@
TEST_NGHTTPX = @TEST_NGHTTPX@
VERSION = @VERSION@
VERSIONNUM = @VERSIONNUM@
VSFTPD = @VSFTPD@
ZLIB_LIBS = @ZLIB_LIBS@
ZSH_FUNCTIONS_DIR = @ZSH_FUNCTIONS_DIR@
abs_builddir = @abs_builddir@
abs_srcdir = @abs_srcdir@
abs_top_builddir = @abs_top_builddir@
abs_top_srcdir = @abs_top_srcdir@
ac_ct_AR = @ac_ct_AR@
ac_ct_CC = @ac_ct_CC@
ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
am__include = @am__include@
am__leading_dot = @am__leading_dot@
am__quote = @am__quote@
am__tar = @am__tar@
am__untar = @am__untar@
bindir = @bindir@
build = @build@
build_alias = @build_alias@
build_cpu = @build_cpu@
build_os = @build_os@
build_vendor = @build_vendor@
builddir = @builddir@
datadir = @datadir@
datarootdir = @datarootdir@
docdir = @docdir@
dvidir = @dvidir@
exec_prefix = @exec_prefix@
host = @host@
host_alias = @host_alias@
host_cpu = @host_cpu@
host_os = @host_os@
host_vendor = @host_vendor@
htmldir = @htmldir@
includedir = @includedir@
infodir = @infodir@
install_sh = @install_sh@
libdir = @libdir@
libexecdir = @libexecdir@
libext = @libext@
localedir = @localedir@
localstatedir = @localstatedir@
mandir = @mandir@
mkdir_p = @mkdir_p@
oldincludedir = @oldincludedir@
pdfdir = @pdfdir@
prefix = @prefix@
program_transform_name = @program_transform_name@
psdir = @psdir@
runstatedir = @runstatedir@
sbindir = @sbindir@
sharedstatedir = @sharedstatedir@
srcdir = @srcdir@
sysconfdir = @sysconfdir@
target_alias = @target_alias@
top_build_prefix = @top_build_prefix@
top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
#***************************************************************************
# _ _ ____ _
# Project ___| | | | _ \| |
# / __| | | | |_) | |
# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://curl.se/docs/copyright.html.
#
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
# copies of the Software, and permit persons to whom the Software is
# furnished to do so, under the terms of the COPYING file.
#
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
# KIND, either express or implied.
#
# SPDX-License-Identifier: curl
#
###########################################################################
AUTOMAKE_OPTIONS = foreign nostdinc
# Specify our include paths here, and do it relative to $(top_srcdir) and
# $(top_builddir), to ensure that these paths which belong to the library
# being currently built and tested are searched before the library which
# might possibly already be installed in the system.
#
# $(top_srcdir)/include is for libcurl's external include files
# $(top_builddir)/lib is for libcurl's generated lib/curl_config.h file
# $(top_srcdir)/lib for libcurl's lib/curl_setup.h and other "borrowed" files
# $(srcdir) for the generated bundle source to find included test sources
AM_CPPFLAGS = -I$(top_srcdir)/include \
-I$(top_builddir)/lib \
-I$(top_srcdir)/lib \
-I$(srcdir)
BUNDLE = servers
# Files referenced from the bundle source
FIRST_C = first.c
FIRST_H = first.h
# Common files used by test programs
UTILS_C = getpart.c util.c
UTILS_H =
CURLX_C = \
../../lib/curlx/base64.c \
../../lib/curlx/fopen.c \
../../lib/curlx/inet_ntop.c \
../../lib/curlx/inet_pton.c \
../../lib/curlx/multibyte.c \
../../lib/curlx/nonblock.c \
../../lib/curlx/strerr.c \
../../lib/curlx/strparse.c \
../../lib/curlx/timediff.c \
../../lib/curlx/timeval.c \
../../lib/curlx/version_win32.c \
../../lib/curlx/wait.c \
../../lib/curlx/warnless.c \
../../lib/curlx/winapi.c
# All test servers
TESTS_C = \
dnsd.c \
mqttd.c \
resolve.c \
rtspd.c \
sockfilt.c \
socksd.c \
sws.c \
tftpd.c
# Get BUNDLE, FIRST_C, FIRST_H, UTILS_C, UTILS_H, CURLX_C, TESTS_C variables
EXTRA_DIST = CMakeLists.txt .checksrc $(FIRST_C) $(FIRST_H) $(UTILS_C) $(UTILS_H) $(TESTS_C)
LDADD = @CURL_NETWORK_AND_TIME_LIBS@
CLEANFILES = $(BUNDLE).c
CHECKSRC = $(CS_$(V))
CS_0 = @echo " RUN " $@;
CS_1 =
CS_ = $(CS_0)
all: all-am
.SUFFIXES:
.SUFFIXES: .c .lo .o .obj
$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(srcdir)/Makefile.inc $(am__configure_deps)
@for dep in $?; do \
case '$(am__configure_deps)' in \
*$$dep*) \
( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
&& { if test -f $@; then exit 0; else break; fi; }; \
exit 1;; \
esac; \
done; \
echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign tests/server/Makefile'; \
$(am__cd) $(top_srcdir) && \
$(AUTOMAKE) --foreign tests/server/Makefile
Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
@case '$?' in \
*config.status*) \
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
*) \
echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
esac;
$(srcdir)/Makefile.inc $(am__empty):
$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
$(am__aclocal_m4_deps):
clean-noinstPROGRAMS:
@list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
echo " rm -f" $$list; \
rm -f $$list || exit $$?; \
test -n "$(EXEEXT)" || exit 0; \
list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
echo " rm -f" $$list; \
rm -f $$list
servers$(EXEEXT): $(servers_OBJECTS) $(servers_DEPENDENCIES) $(EXTRA_servers_DEPENDENCIES)
@rm -f servers$(EXEEXT)
$(AM_V_CCLD)$(LINK) $(servers_OBJECTS) $(servers_LDADD) $(LIBS)
mostlyclean-compile:
-rm -f *.$(OBJEXT)
distclean-compile:
-rm -f *.tab.c
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/servers.Po@am__quote@ # am--include-marker
$(am__depfiles_remade):
@$(MKDIR_P) $(@D)
@echo '# dummy' >$@-t && $(am__mv) $@-t $@
am--depfiles: $(am__depfiles_remade)
.c.o:
@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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 $@ $<
.c.obj:
@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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) '$<'`
.c.lo:
@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
mostlyclean-libtool:
-rm -f *.lo
clean-libtool:
-rm -rf .libs _libs
ID: $(am__tagged_files)
$(am__define_uniq_tagged_files); mkid -fID $$unique
tags: tags-am
TAGS: tags
tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
set x; \
here=`pwd`; \
$(am__define_uniq_tagged_files); \
shift; \
if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
test -n "$$unique" || unique=$$empty_fix; \
if test $$# -gt 0; then \
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
"$$@" $$unique; \
else \
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
$$unique; \
fi; \
fi
ctags: ctags-am
CTAGS: ctags
ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
$(am__define_uniq_tagged_files); \
test -z "$(CTAGS_ARGS)$$unique" \
|| $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
$$unique
GTAGS:
here=`$(am__cd) $(top_builddir) && pwd` \
&& $(am__cd) $(top_srcdir) \
&& gtags -i $(GTAGS_ARGS) "$$here"
cscopelist: cscopelist-am
cscopelist-am: $(am__tagged_files)
list='$(am__tagged_files)'; \
case "$(srcdir)" in \
[\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
*) sdir=$(subdir)/$(srcdir) ;; \
esac; \
for i in $$list; do \
if test -f "$$i"; then \
echo "$(subdir)/$$i"; \
else \
echo "$$sdir/$$i"; \
fi; \
done >> $(top_builddir)/cscope.files
distclean-tags:
-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
distdir: $(BUILT_SOURCES)
$(MAKE) $(AM_MAKEFLAGS) distdir-am
distdir-am: $(DISTFILES)
@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
list='$(DISTFILES)'; \
dist_files=`for file in $$list; do echo $$file; done | \
sed -e "s|^$$srcdirstrip/||;t" \
-e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
case $$dist_files in \
*/*) $(MKDIR_P) `echo "$$dist_files" | \
sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
sort -u` ;; \
esac; \
for file in $$dist_files; do \
if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
if test -d $$d/$$file; then \
dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
if test -d "$(distdir)/$$file"; then \
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
fi; \
if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
fi; \
cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
else \
test -f "$(distdir)/$$file" \
|| cp -p $$d/$$file "$(distdir)/$$file" \
|| exit 1; \
fi; \
done
check-am: all-am
check: check-am
@NOT_CURL_CI_FALSE@all-local:
all-am: Makefile $(PROGRAMS) all-local
installdirs:
install: install-am
install-exec: install-exec-am
install-data: install-data-am
uninstall: uninstall-am
install-am: all-am
@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
installcheck: installcheck-am
install-strip:
if test -z '$(STRIP)'; then \
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
install; \
else \
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
"INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
fi
mostlyclean-generic:
clean-generic:
-test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
distclean-generic:
-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
maintainer-clean-generic:
@echo "This command is intended for maintainers to use"
@echo "it deletes files that may require special tools to rebuild."
clean: clean-am
clean-am: clean-generic clean-libtool clean-local clean-noinstPROGRAMS \
mostlyclean-am
distclean: distclean-am
-rm -f ./$(DEPDIR)/servers.Po
-rm -f Makefile
distclean-am: clean-am distclean-compile distclean-generic \
distclean-tags
dvi: dvi-am
dvi-am:
html: html-am
html-am:
info: info-am
info-am:
install-data-am:
install-dvi: install-dvi-am
install-dvi-am:
install-exec-am:
install-html: install-html-am
install-html-am:
install-info: install-info-am
install-info-am:
install-man:
install-pdf: install-pdf-am
install-pdf-am:
install-ps: install-ps-am
install-ps-am:
installcheck-am:
maintainer-clean: maintainer-clean-am
-rm -f ./$(DEPDIR)/servers.Po
-rm -f Makefile
maintainer-clean-am: distclean-am maintainer-clean-generic
mostlyclean: mostlyclean-am
mostlyclean-am: mostlyclean-compile mostlyclean-generic \
mostlyclean-libtool
pdf: pdf-am
pdf-am:
ps: ps-am
ps-am:
uninstall-am:
.MAKE: install-am install-strip
.PHONY: CTAGS GTAGS TAGS all all-am all-local am--depfiles check \
check-am clean clean-generic clean-libtool clean-local \
clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \
distclean-compile distclean-generic distclean-libtool \
distclean-tags distdir dvi dvi-am html html-am info info-am \
install install-am install-data install-data-am install-dvi \
install-dvi-am install-exec install-exec-am install-html \
install-html-am install-info install-info-am install-man \
install-pdf install-pdf-am install-ps install-ps-am \
install-strip installcheck installcheck-am installdirs \
maintainer-clean maintainer-clean-generic mostlyclean \
mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am
.PRECIOUS: Makefile
$(BUNDLE).c: $(top_srcdir)/scripts/mk-unity.pl Makefile.inc $(FIRST_C) $(UTILS_C) $(CURLX_C) $(TESTS_C)
@PERL@ $(top_srcdir)/scripts/mk-unity.pl --include $(UTILS_C) $(CURLX_C) --test $(TESTS_C) > $(BUNDLE).c
# ignore generated C files since they play by slightly different rules!
checksrc:
$(CHECKSRC)(@PERL@ $(top_srcdir)/scripts/checksrc.pl -D$(srcdir) \
-W$(srcdir)/$(BUNDLE).c \
$(srcdir)/*.[ch])
@NOT_CURL_CI_TRUE@all-local: checksrc
clean-local:
rm -f $(BUNDLE)
# Tell versions [3.59,3.63) of GNU make to not export all variables.
# Otherwise a system limit (for SysV at least) may be exceeded.
.NOEXPORT:
+61
View File
@@ -0,0 +1,61 @@
#***************************************************************************
# _ _ ____ _
# Project ___| | | | _ \| |
# / __| | | | |_) | |
# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://curl.se/docs/copyright.html.
#
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
# copies of the Software, and permit persons to whom the Software is
# furnished to do so, under the terms of the COPYING file.
#
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
# KIND, either express or implied.
#
# SPDX-License-Identifier: curl
#
###########################################################################
# Shared between CMakeLists.txt and Makefile.am
BUNDLE = servers
# Files referenced from the bundle source
FIRST_C = first.c
FIRST_H = first.h
# Common files used by test programs
UTILS_C = getpart.c util.c
UTILS_H =
CURLX_C = \
../../lib/curlx/base64.c \
../../lib/curlx/fopen.c \
../../lib/curlx/inet_ntop.c \
../../lib/curlx/inet_pton.c \
../../lib/curlx/multibyte.c \
../../lib/curlx/nonblock.c \
../../lib/curlx/strerr.c \
../../lib/curlx/strparse.c \
../../lib/curlx/timediff.c \
../../lib/curlx/timeval.c \
../../lib/curlx/version_win32.c \
../../lib/curlx/wait.c \
../../lib/curlx/warnless.c \
../../lib/curlx/winapi.c
# All test servers
TESTS_C = \
dnsd.c \
mqttd.c \
resolve.c \
rtspd.c \
sockfilt.c \
socksd.c \
sws.c \
tftpd.c
+681
View File
@@ -0,0 +1,681 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "first.h"
static int dnsd_wrotepidfile = 0;
static int dnsd_wroteportfile = 0;
static unsigned short get16bit(const unsigned char **pkt,
size_t *size)
{
const unsigned char *p = *pkt;
(*pkt) += 2;
*size -= 2;
return (unsigned short)((p[0] << 8) | p[1]);
}
static char name[256];
static int qname(const unsigned char **pkt, size_t *size)
{
unsigned char length;
int o = 0;
const unsigned char *p = *pkt;
do {
int i;
length = *p++;
if(*size < length)
/* too long */
return 1;
if(length && o)
name[o++] = '.';
for(i = 0; i < length; i++) {
name[o++] = *p++;
}
} while(length);
*size -= (p - *pkt);
*pkt = p;
name[o++] = '\0';
return 0;
}
#define QTYPE_A 1
#define QTYPE_AAAA 28
#define QTYPE_HTTPS 0x41
static const char *type2string(unsigned short qtype)
{
switch(qtype) {
case QTYPE_A:
return "A";
case QTYPE_AAAA:
return "AAAA";
case QTYPE_HTTPS:
return "HTTPS";
}
return "<unknown>";
}
/*
* Handle initial connection protocol.
*
* Return query (qname + type + class), type and id.
*/
static int store_incoming(const unsigned char *data, size_t size,
unsigned char *qbuf, size_t qbuflen, size_t *qlen,
unsigned short *qtype, unsigned short *idp)
{
FILE *server;
char dumpfile[256];
#if 0
size_t i;
#endif
unsigned short qd;
const unsigned char *qptr;
size_t qsize;
*qlen = 0;
*qtype = 0;
*idp = 0;
snprintf(dumpfile, sizeof(dumpfile), "%s/dnsd.input", logdir);
/* Open request dump file. */
server = fopen(dumpfile, "ab");
if(!server) {
char errbuf[STRERROR_LEN];
int error = errno;
logmsg("fopen() failed with error (%d) %s",
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
logmsg("Error opening file '%s'", dumpfile);
return -1;
}
/*
1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ID |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR| Opcode |AA|TC|RD|RA| Z | RCODE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| QDCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ANCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| NSCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ARCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*/
*idp = get16bit(&data, &size);
data += 2; /* skip the next 16 bits */
size -= 2;
#if 0
fprintf(server, "QR: %x\n", (id & 0x8000) > 15);
fprintf(server, "OPCODE: %x\n", (id & 0x7800) >> 11);
fprintf(server, "TC: %x\n", (id & 0x200) >> 9);
fprintf(server, "RD: %x\n", (id & 0x100) >> 8);
fprintf(server, "Z: %x\n", (id & 0x70) >> 4);
fprintf(server, "RCODE: %x\n", (id & 0x0f));
#endif
(void) get16bit(&data, &size);
data += 6; /* skip ANCOUNT, NSCOUNT and ARCOUNT */
size -= 6;
/* store pointer and size at the QD point */
qsize = size;
qptr = data;
if(!qname(&data, &size)) {
qd = get16bit(&data, &size);
fprintf(server, "QNAME %s QTYPE %s\n", name, type2string(qd));
*qtype = qd;
logmsg("Question for '%s' type %x / %s", name, qd,
type2string(qd));
(void) get16bit(&data, &size);
*qlen = qsize - size; /* total size of the query */
if(*qlen > qbuflen) {
logmsg("dnsd: query too large: %lu > %lu",
(unsigned long)*qlen, (unsigned long)qbuflen);
fclose(server);
return -1;
}
memcpy(qbuf, qptr, *qlen);
}
else
logmsg("Bad input qname");
#if 0
for(i = 0; i < size; i++) {
fprintf(server, "%02d", (unsigned int)data[i]);
}
fprintf(server, "\n");
#endif
fclose(server);
return 0;
}
static void add_answer(unsigned char *bytes, size_t *w,
const unsigned char *a, size_t alen,
unsigned short qtype)
{
size_t i = *w;
/* add answer */
bytes[i++] = 0xc0;
bytes[i++] = 0x0c; /* points to the query at this fixed packet index */
/* QTYPE */
bytes[i++] = (unsigned char)(qtype >> 8);
bytes[i++] = (unsigned char)(qtype & 0xff);
/* QCLASS IN */
bytes[i++] = 0x00;
bytes[i++] = 0x01;
/* TTL, Time to live: 2580 (43 minutes) */
bytes[i++] = 0x00;
bytes[i++] = 0x00;
bytes[i++] = 0x0a;
bytes[i++] = 0x14;
/* QTYPE size */
bytes[i++] = (unsigned char)(alen >> 8);
bytes[i++] = (unsigned char)(alen & 0xff);
memcpy(&bytes[i], a, alen);
i += alen;
*w = i;
}
#ifdef _WIN32
#define SENDTO3 int
#else
#define SENDTO3 size_t
#endif
#define INSTRUCTIONS "dnsd.cmd"
#define MAX_ALPN 5
static unsigned char ipv4_pref[4];
static unsigned char ipv6_pref[16];
static unsigned char alpn_pref[MAX_ALPN];
static int alpn_count;
static unsigned char ancount_a;
static unsigned char ancount_aaaa;
/* this is an answer to a question */
static int send_response(curl_socket_t sock,
const struct sockaddr *addr, curl_socklen_t addrlen,
unsigned char *qbuf, size_t qlen,
unsigned short qtype, unsigned short id)
{
ssize_t rc;
size_t i;
int a;
char addrbuf[128]; /* IP address buffer */
unsigned char bytes[256] = {
0x80, 0xea, /* ID, overwrite */
0x81, 0x80,
/*
Flags: 0x8180 Standard query response, No error
1... .... .... .... = Response: Message is a response
.000 0... .... .... = Opcode: Standard query (0)
.... .0.. .... .... = Authoritative: Server is not an authority for
domain
.... ..0. .... .... = Truncated: Message is not truncated
.... ...1 .... .... = Recursion desired: Do query recursively
.... .... 1... .... = Recursion available: Server can do recursive
queries
.... .... .0.. .... = Z: reserved (0)
.... .... ..0. .... = Answer authenticated: Answer/authority portion
was not authenticated by the server
.... .... ...0 .... = Non-authenticated data: Unacceptable
.... .... .... 0000 = Reply code: No error (0)
*/
0x0, 0x1, /* QDCOUNT a single question */
0x0, 0x0, /* ANCOUNT number of answers */
0x0, 0x0, /* NSCOUNT */
0x0, 0x0 /* ARCOUNT */
};
bytes[0] = (unsigned char)(id >> 8);
bytes[1] = (unsigned char)(id & 0xff);
if(qlen > (sizeof(bytes) - 12))
return -1;
/* append query, includes QTYPE and QCLASS */
memcpy(&bytes[12], qbuf, qlen);
i = 12 + qlen;
switch(qtype) {
case QTYPE_A:
bytes[7] = ancount_a;
for(a = 0; a < ancount_a; a++) {
const unsigned char *store = ipv4_pref;
add_answer(bytes, &i, store, sizeof(ipv4_pref), QTYPE_A);
logmsg("Sending back A (%x) '%s'", QTYPE_A,
curlx_inet_ntop(AF_INET, store, addrbuf, sizeof(addrbuf)));
}
break;
case QTYPE_AAAA:
bytes[7] = ancount_aaaa;
for(a = 0; a < ancount_aaaa; a++) {
const unsigned char *store = ipv6_pref;
add_answer(bytes, &i, store, sizeof(ipv6_pref), QTYPE_AAAA);
logmsg("Sending back AAAA (%x) '%s'", QTYPE_AAAA,
curlx_inet_ntop(AF_INET6, store, addrbuf, sizeof(addrbuf)));
}
break;
case QTYPE_HTTPS:
bytes[7] = 1; /* one answer */
break;
}
#ifdef __AMIGA__
/* Amiga breakage */
(void)rc;
(void)sock;
(void)addr;
(void)addrlen;
fprintf(stderr, "Not working\n");
return -1;
#else
rc = sendto(sock, (const void *)bytes, (SENDTO3) i, 0, addr, addrlen);
if(rc != (ssize_t)i) {
fprintf(stderr, "failed sending %d bytes\n", (int)i);
}
#endif
return 0;
}
static void read_instructions(void)
{
char file[256];
FILE *f;
snprintf(file, sizeof(file), "%s/" INSTRUCTIONS, logdir);
f = fopen(file, FOPEN_READTEXT);
if(f) {
char buf[256];
ancount_aaaa = ancount_a = 0;
alpn_count = 0;
while(fgets(buf, sizeof(buf), f)) {
char *p = strchr(buf, '\n');
if(p) {
int rc;
*p = 0;
if(!strncmp("A: ", buf, 3)) {
rc = curlx_inet_pton(AF_INET, &buf[3], ipv4_pref);
ancount_a = (rc == 1);
}
else if(!strncmp("AAAA: ", buf, 6)) {
char *p6 = &buf[6];
if(*p6 == '[') {
char *pt = strchr(p6, ']');
if(pt)
*pt = 0;
p6++;
}
rc = curlx_inet_pton(AF_INET6, p6, ipv6_pref);
ancount_aaaa = (rc == 1);
}
else if(!strncmp("ALPN: ", buf, 6)) {
char *ap = &buf[6];
rc = 0;
while(*ap) {
if('h' == *ap) {
ap++;
if(*ap >= '1' && *ap <= '3') {
if(alpn_count < MAX_ALPN)
alpn_pref[alpn_count++] = *ap;
}
else
break;
}
else
break;
}
}
else {
rc = 0;
}
if(rc != 1) {
logmsg("Bad line in %s: '%s'\n", file, buf);
}
}
}
fclose(f);
}
else
logmsg("Error opening file '%s'", file);
}
static int test_dnsd(int argc, char **argv)
{
srvr_sockaddr_union_t me;
ssize_t n = 0;
int arg = 1;
unsigned short port = 9123; /* UDP */
curl_socket_t sock = CURL_SOCKET_BAD;
int flag;
int rc;
int error;
char errbuf[STRERROR_LEN];
int result = 0;
pidname = ".dnsd.pid";
serverlogfile = "log/dnsd.log";
serverlogslocked = 0;
while(argc > arg) {
if(!strcmp("--verbose", argv[arg])) {
arg++;
/* nothing yet */
}
else if(!strcmp("--version", argv[arg])) {
printf("dnsd IPv4%s\n",
#ifdef USE_IPV6
"/IPv6"
#else
""
#endif
);
return 0;
}
else if(!strcmp("--pidfile", argv[arg])) {
arg++;
if(argc > arg)
pidname = argv[arg++];
}
else if(!strcmp("--portfile", argv[arg])) {
arg++;
if(argc > arg)
portname = argv[arg++];
}
else if(!strcmp("--logfile", argv[arg])) {
arg++;
if(argc > arg)
serverlogfile = argv[arg++];
}
else if(!strcmp("--logdir", argv[arg])) {
arg++;
if(argc > arg)
logdir = argv[arg++];
}
else if(!strcmp("--ipv4", argv[arg])) {
#ifdef USE_IPV6
ipv_inuse = "IPv4";
use_ipv6 = FALSE;
#endif
arg++;
}
else if(!strcmp("--ipv6", argv[arg])) {
#ifdef USE_IPV6
ipv_inuse = "IPv6";
use_ipv6 = TRUE;
#endif
arg++;
}
else if(!strcmp("--port", argv[arg])) {
arg++;
if(argc > arg) {
port = (unsigned short)atoi(argv[arg]);
arg++;
}
}
else {
if(argv[arg])
fprintf(stderr, "unknown option: %s\n", argv[arg]);
puts("Usage: dnsd [option]\n"
" --version\n"
" --logfile [file]\n"
" --logdir [directory]\n"
" --pidfile [file]\n"
" --portfile [file]\n"
" --ipv4\n"
" --ipv6\n"
" --port [port]\n");
return 0;
}
}
snprintf(loglockfile, sizeof(loglockfile), "%s/%s/dnsd-%s.lock",
logdir, SERVERLOGS_LOCKDIR, ipv_inuse);
#ifdef _WIN32
if(win32_init())
return 2;
#endif
#ifdef USE_IPV6
if(!use_ipv6)
#endif
sock = socket(AF_INET, SOCK_DGRAM, 0);
#ifdef USE_IPV6
else
sock = socket(AF_INET6, SOCK_DGRAM, 0);
#endif
if(CURL_SOCKET_BAD == sock) {
error = SOCKERRNO;
logmsg("Error creating socket (%d) %s",
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
result = 1;
goto dnsd_cleanup;
}
flag = 1;
if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
(void *)&flag, sizeof(flag))) {
error = SOCKERRNO;
logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s",
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
result = 1;
goto dnsd_cleanup;
}
#ifdef USE_IPV6
if(!use_ipv6) {
#endif
memset(&me.sa4, 0, sizeof(me.sa4));
me.sa4.sin_family = AF_INET;
me.sa4.sin_addr.s_addr = INADDR_ANY;
me.sa4.sin_port = htons(port);
rc = bind(sock, &me.sa, sizeof(me.sa4));
#ifdef USE_IPV6
}
else {
memset(&me.sa6, 0, sizeof(me.sa6));
me.sa6.sin6_family = AF_INET6;
me.sa6.sin6_addr = in6addr_any;
me.sa6.sin6_port = htons(port);
rc = bind(sock, &me.sa, sizeof(me.sa6));
}
#endif /* USE_IPV6 */
if(rc) {
error = SOCKERRNO;
logmsg("Error binding socket on port %hu (%d) %s", port,
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
result = 1;
goto dnsd_cleanup;
}
if(!port) {
/* The system was supposed to choose a port number, figure out which
port we actually got and update the listener port value with it. */
curl_socklen_t la_size;
srvr_sockaddr_union_t localaddr;
#ifdef USE_IPV6
if(!use_ipv6)
#endif
la_size = sizeof(localaddr.sa4);
#ifdef USE_IPV6
else
la_size = sizeof(localaddr.sa6);
#endif
memset(&localaddr.sa, 0, (size_t)la_size);
if(getsockname(sock, &localaddr.sa, &la_size) < 0) {
error = SOCKERRNO;
logmsg("getsockname() failed with error (%d) %s",
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
sclose(sock);
goto dnsd_cleanup;
}
switch(localaddr.sa.sa_family) {
case AF_INET:
port = ntohs(localaddr.sa4.sin_port);
break;
#ifdef USE_IPV6
case AF_INET6:
port = ntohs(localaddr.sa6.sin6_port);
break;
#endif
default:
break;
}
if(!port) {
/* Real failure, listener port shall not be zero beyond this point. */
logmsg("Apparently getsockname() succeeded, with listener port zero.");
logmsg("A valid reason for this failure is a binary built without");
logmsg("proper network library linkage. This might not be the only");
logmsg("reason, but double check it before anything else.");
result = 2;
goto dnsd_cleanup;
}
}
dnsd_wrotepidfile = write_pidfile(pidname);
if(!dnsd_wrotepidfile) {
result = 1;
goto dnsd_cleanup;
}
if(portname) {
dnsd_wroteportfile = write_portfile(portname, port);
if(!dnsd_wroteportfile) {
result = 1;
goto dnsd_cleanup;
}
}
logmsg("Running %s version on port UDP/%d", ipv_inuse, (int)port);
for(;;) {
unsigned short id = 0;
unsigned char inbuffer[1500];
srvr_sockaddr_union_t from;
curl_socklen_t fromlen;
unsigned char qbuf[256]; /* query storage */
size_t qlen = 0; /* query size */
unsigned short qtype = 0;
fromlen = sizeof(from);
#ifdef USE_IPV6
if(!use_ipv6)
#endif
fromlen = sizeof(from.sa4);
#ifdef USE_IPV6
else
fromlen = sizeof(from.sa6);
#endif
n = (ssize_t)recvfrom(sock, (char *)inbuffer, sizeof(inbuffer), 0,
&from.sa, &fromlen);
if(got_exit_signal)
break;
if(n < 0) {
logmsg("recvfrom");
result = 3;
break;
}
/* read once per incoming query, which is probably more than one
per test case */
read_instructions();
store_incoming(inbuffer, n, qbuf, sizeof(qbuf), &qlen, &qtype, &id);
set_advisor_read_lock(loglockfile);
serverlogslocked = 1;
send_response(sock, &from.sa, fromlen, qbuf, qlen, qtype, id);
if(got_exit_signal)
break;
if(serverlogslocked) {
serverlogslocked = 0;
clear_advisor_read_lock(loglockfile);
}
/* logmsg("end of one transfer"); */
}
dnsd_cleanup:
#if 0
if((peer != sock) && (peer != CURL_SOCKET_BAD))
sclose(peer);
#endif
if(sock != CURL_SOCKET_BAD)
sclose(sock);
if(got_exit_signal)
logmsg("signalled to die");
if(dnsd_wrotepidfile)
unlink(pidname);
if(dnsd_wroteportfile)
unlink(portname);
if(serverlogslocked) {
serverlogslocked = 0;
clear_advisor_read_lock(loglockfile);
}
restore_signal_handlers(true);
if(got_exit_signal) {
logmsg("========> %s dnsd (port: %d pid: %ld) exits with signal (%d)",
ipv_inuse, (int)port, (long)our_getpid(), exit_signal);
/*
* To properly set the return status of the process we
* must raise the same signal SIGINT or SIGTERM that we
* caught and let the old handler take care of it.
*/
raise(exit_signal);
}
logmsg("========> dnsd quits");
return result;
}
+55
View File
@@ -0,0 +1,55 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "first.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
entry_func_t entry_func;
char *entry_name;
size_t tmp;
if(argc < 2) {
fprintf(stderr, "Pass servername as first argument\n");
return 1;
}
entry_name = argv[1];
entry_func = NULL;
for(tmp = 0; s_entries[tmp].ptr; ++tmp) {
if(strcmp(entry_name, s_entries[tmp].name) == 0) {
entry_func = s_entries[tmp].ptr;
break;
}
}
if(!entry_func) {
fprintf(stderr, "Test '%s' not found.\n", entry_name);
return 99;
}
return entry_func(argc - 1, argv + 1);
}
+174
View File
@@ -0,0 +1,174 @@
#ifndef HEADER_SERVER_FIRST_H
#define HEADER_SERVER_FIRST_H
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
/* Test servers simply are standalone programs that do not use libcurl
* library. For convenience and to ease portability of these servers,
* some source code files from the libcurl subdirectory are also used
* to build the servers. In order to achieve proper linkage of these
* files on Windows targets it is necessary to build the test servers
* with CURL_STATICLIB defined, independently of how libcurl is built.
* For other platforms, this macro is a no-op and safe to set.
*/
#define CURL_STATICLIB
#define WITHOUT_LIBCURL
#define CURL_NO_OLDIES
#include "curl_setup.h"
typedef int (*entry_func_t)(int, char **);
struct entry_s {
const char *name;
entry_func_t ptr;
};
extern const struct entry_s s_entries[];
#ifndef UNDER_CE
#include <signal.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_NETINET_IN6_H
#include <netinet/in6.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#include <curlx/curlx.h>
/* adjust for old MSVC */
#if defined(_MSC_VER) && (_MSC_VER < 1900)
# define snprintf _snprintf
#endif
#ifdef _WIN32
# define CURL_STRNICMP(p1, p2, n) _strnicmp(p1, p2, n)
#elif defined(HAVE_STRCASECMP)
# ifdef HAVE_STRINGS_H
# include <strings.h>
# endif
# define CURL_STRNICMP(p1, p2, n) strncasecmp(p1, p2, n)
#elif defined(HAVE_STRCMPI)
# define CURL_STRNICMP(p1, p2, n) strncmpi(p1, p2, n)
#elif defined(HAVE_STRICMP)
# define CURL_STRNICMP(p1, p2, n) strnicmp(p1, p2, n)
#else
# error "missing case insensitive comparison function"
#endif
enum {
DOCNUMBER_NOTHING = -7,
DOCNUMBER_QUIT = -6,
DOCNUMBER_BADCONNECT = -5,
DOCNUMBER_INTERNAL = -4,
DOCNUMBER_CONNECT = -3,
DOCNUMBER_WERULEZ = -2,
DOCNUMBER_404 = -1
};
#include <curl/curl.h> /* for curl_socket_t */
#ifdef USE_UNIX_SOCKETS
#ifdef HAVE_SYS_UN_H
#include <sys/un.h> /* for sockaddr_un */
#endif
#endif /* USE_UNIX_SOCKETS */
typedef union {
struct sockaddr sa;
struct sockaddr_in sa4;
#ifdef USE_IPV6
struct sockaddr_in6 sa6;
#endif
#ifdef USE_UNIX_SOCKETS
struct sockaddr_un sau;
#endif
} srvr_sockaddr_union_t;
/* getpart */
#define GPE_NO_BUFFER_SPACE -2
#define GPE_OUT_OF_MEMORY -1
#define GPE_OK 0
#define GPE_END_OF_FILE 1
extern int getpart(char **outbuf, size_t *outlen,
const char *main, const char *sub, FILE *stream);
/* utility functions */
extern char *data_to_hex(char *data, size_t len);
extern void logmsg(const char *msg, ...);
extern void loghex(unsigned char *buffer, ssize_t len);
extern unsigned char byteval(char *value);
extern int win32_init(void);
extern FILE *test2fopen(long testno, const char *logdir2);
extern curl_off_t our_getpid(void);
extern int write_pidfile(const char *filename);
extern int write_portfile(const char *filename, int port);
extern void set_advisor_read_lock(const char *filename);
extern void clear_advisor_read_lock(const char *filename);
static volatile int got_exit_signal = 0;
static volatile int exit_signal = 0;
#ifdef _WIN32
static HANDLE exit_event = NULL;
#endif
extern void install_signal_handlers(bool keep_sigalrm);
extern void restore_signal_handlers(bool keep_sigalrm);
#ifdef USE_UNIX_SOCKETS
extern int bind_unix_socket(curl_socket_t sock, const char *unix_socket,
struct sockaddr_un *sau);
#endif
extern curl_socket_t sockdaemon(curl_socket_t sock,
unsigned short *listenport,
const char *unix_socket,
bool bind_only);
/* global variables */
static const char *srcpath = "."; /* pointing to the test dir */
static const char *pidname = NULL;
static const char *portname = NULL; /* none by default */
static const char *serverlogfile = NULL;
static int serverlogslocked;
static const char *configfile = NULL;
static const char *logdir = "log";
static char loglockfile[256];
#ifdef USE_IPV6
static bool use_ipv6 = FALSE;
#endif
static const char *ipv_inuse = "IPv4";
static unsigned short server_port = 0;
static const char *socket_type = "IPv4";
static int socket_domain = AF_INET;
#define SERVERLOGS_LOCKDIR "lock" /* within logdir */
#endif /* HEADER_SERVER_FIRST_H */
+449
View File
@@ -0,0 +1,449 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "first.h"
#define EAT_SPACE(p) while(*(p) && ISSPACE(*(p))) (p)++
#define EAT_WORD(p) while(*(p) && !ISSPACE(*(p)) && ('>' != *(p))) (p)++
#ifdef DEBUG_GETPART
#define show(x) printf x
#else
#define show(x) Curl_nop_stmt
#endif
/*
* line_length()
*
* Counts the number of characters in a line including a new line.
* Unlike strlen() it does not stop at nul bytes.
*
*/
static size_t line_length(const char *buffer, int bytestocheck)
{
size_t length = 1;
while(*buffer != '\n' && --bytestocheck) {
length++;
buffer++;
}
if(*buffer != '\n') {
/*
* We didn't find a new line so the last byte must be a
* '\0' character inserted by fgets() which we should not
* count.
*/
length--;
}
return length;
}
/*
* readline()
*
* Reads a complete line from a file into a dynamically allocated buffer.
*
* Calling function may call this multiple times with same 'buffer'
* and 'bufsize' pointers to avoid multiple buffer allocations. Buffer
* will be reallocated and 'bufsize' increased until whole line fits in
* buffer before returning it.
*
* Calling function is responsible to free allocated buffer.
*
* This function may return:
* GPE_OUT_OF_MEMORY
* GPE_END_OF_FILE
* GPE_OK
*/
static int readline(char **buffer, size_t *bufsize, size_t *length,
FILE *stream)
{
size_t offset = 0;
char *newptr;
if(!*buffer) {
*buffer = calloc(1, 128);
if(!*buffer)
return GPE_OUT_OF_MEMORY;
*bufsize = 128;
}
for(;;) {
int bytestoread = curlx_uztosi(*bufsize - offset);
if(!fgets(*buffer + offset, bytestoread, stream)) {
*length = 0;
return (offset != 0) ? GPE_OK : GPE_END_OF_FILE;
}
*length = offset + line_length(*buffer + offset, bytestoread);
if(*(*buffer + *length - 1) == '\n')
break;
offset = *length;
if(*length < *bufsize - 1)
continue;
newptr = realloc(*buffer, *bufsize * 2);
if(!newptr)
return GPE_OUT_OF_MEMORY;
memset(&newptr[*bufsize], 0, *bufsize);
*buffer = newptr;
*bufsize *= 2;
}
return GPE_OK;
}
/*
* appenddata()
*
* This appends data from a given source buffer to the end of the used part of
* a destination buffer. Arguments relative to the destination buffer are, the
* address of a pointer to the destination buffer 'dst_buf', the length of data
* in destination buffer excluding potential null string termination 'dst_len',
* the allocated size of destination buffer 'dst_alloc'. All three destination
* buffer arguments may be modified by this function. Arguments relative to the
* source buffer are, a pointer to the source buffer 'src_buf' and indication
* whether the source buffer is base64 encoded or not 'src_b64'.
*
* If the source buffer is indicated to be base64 encoded, this appends the
* decoded data, binary or whatever, to the destination. The source buffer
* may not hold binary data, only a null-terminated string is valid content.
*
* Destination buffer will be enlarged and relocated as needed.
*
* Calling function is responsible to provide preallocated destination
* buffer and also to deallocate it when no longer needed.
*
* This function may return:
* GPE_OUT_OF_MEMORY
* GPE_OK
*/
static int appenddata(char **dst_buf, /* dest buffer */
size_t *dst_len, /* dest buffer data length */
size_t *dst_alloc, /* dest buffer allocated size */
char *src_buf, /* source buffer */
size_t src_len, /* source buffer length */
int src_b64) /* != 0 if source is base64 encoded */
{
size_t need_alloc = 0;
if(!src_len)
return GPE_OK;
need_alloc = src_len + *dst_len + 1;
if(src_b64) {
if(src_buf[src_len - 1] == '\r')
src_len--;
if(src_buf[src_len - 1] == '\n')
src_len--;
}
/* enlarge destination buffer if required */
if(need_alloc > *dst_alloc) {
size_t newsize = need_alloc * 2;
char *newptr = realloc(*dst_buf, newsize);
if(!newptr) {
return GPE_OUT_OF_MEMORY;
}
*dst_alloc = newsize;
*dst_buf = newptr;
}
/* memcpy to support binary blobs */
memcpy(*dst_buf + *dst_len, src_buf, src_len);
*dst_len += src_len;
*(*dst_buf + *dst_len) = '\0';
return GPE_OK;
}
static int decodedata(char **buf, /* dest buffer */
size_t *len) /* dest buffer data length */
{
CURLcode error = CURLE_OK;
unsigned char *buf64 = NULL;
size_t src_len = 0;
if(!*len)
return GPE_OK;
/* base64 decode the given buffer */
error = curlx_base64_decode(*buf, &buf64, &src_len);
if(error)
return GPE_OUT_OF_MEMORY;
if(!src_len) {
/*
** currently there is no way to tell apart an OOM condition in
** curlx_base64_decode() from zero length decoded data. For now,
** let's just assume it is an OOM condition, currently we have
** no input for this function that decodes to zero length data.
*/
free(buf64);
return GPE_OUT_OF_MEMORY;
}
/* memcpy to support binary blobs */
memcpy(*buf, buf64, src_len);
*len = src_len;
*(*buf + src_len) = '\0';
free(buf64);
return GPE_OK;
}
/*
* getpart()
*
* This returns whole contents of specified XML-like section and subsection
* from the given file. This is mostly used to retrieve a specific part from
* a test definition file for consumption by test suite servers.
*
* Data is returned in a dynamically allocated buffer, a pointer to this data
* and the size of the data is stored at the addresses that caller specifies.
*
* If the returned data is a string the returned size will be the length of
* the string excluding null-termination. Otherwise it will just be the size
* of the returned binary data.
*
* Calling function is responsible to free returned buffer.
*
* This function may return:
* GPE_NO_BUFFER_SPACE
* GPE_OUT_OF_MEMORY
* GPE_OK
*/
int getpart(char **outbuf, size_t *outlen,
const char *main, const char *sub, FILE *stream)
{
# define MAX_TAG_LEN 200
char curouter[MAX_TAG_LEN + 1]; /* current outermost section */
char curmain[MAX_TAG_LEN + 1]; /* current main section */
char cursub[MAX_TAG_LEN + 1]; /* current sub section */
char ptag[MAX_TAG_LEN + 1]; /* potential tag */
char patt[MAX_TAG_LEN + 1]; /* potential attributes */
char *buffer = NULL;
char *ptr;
char *end;
union {
ssize_t sig;
size_t uns;
} len;
size_t bufsize = 0;
size_t outalloc = 256;
size_t datalen;
int in_wanted_part = 0;
int base64 = 0;
int nonewline = 0;
int error;
enum {
STATE_OUTSIDE = 0,
STATE_OUTER = 1,
STATE_INMAIN = 2,
STATE_INSUB = 3,
STATE_ILLEGAL = 4
} state = STATE_OUTSIDE;
*outlen = 0;
*outbuf = malloc(outalloc);
if(!*outbuf)
return GPE_OUT_OF_MEMORY;
*(*outbuf) = '\0';
curouter[0] = curmain[0] = cursub[0] = ptag[0] = patt[0] = '\0';
while((error = readline(&buffer, &bufsize, &datalen, stream)) == GPE_OK) {
ptr = buffer;
EAT_SPACE(ptr);
if('<' != *ptr) {
if(in_wanted_part) {
show(("=> %s", buffer));
error = appenddata(outbuf, outlen, &outalloc, buffer, datalen,
base64);
if(error)
break;
}
continue;
}
ptr++;
if('/' == *ptr) {
/*
** closing section tag
*/
ptr++;
end = ptr;
EAT_WORD(end);
len.sig = end - ptr;
if(len.sig > MAX_TAG_LEN) {
error = GPE_NO_BUFFER_SPACE;
break;
}
memcpy(ptag, ptr, len.uns);
ptag[len.uns] = '\0';
if((STATE_INSUB == state) && !strcmp(cursub, ptag)) {
/* end of current sub section */
state = STATE_INMAIN;
cursub[0] = '\0';
if(in_wanted_part) {
/* Do we need to base64 decode the data? */
if(base64) {
error = decodedata(outbuf, outlen);
if(error)
return error;
}
if(nonewline)
(*outlen)--;
break;
}
}
else if((STATE_INMAIN == state) && !strcmp(curmain, ptag)) {
/* end of current main section */
state = STATE_OUTER;
curmain[0] = '\0';
if(in_wanted_part) {
/* Do we need to base64 decode the data? */
if(base64) {
error = decodedata(outbuf, outlen);
if(error)
return error;
}
if(nonewline)
(*outlen)--;
break;
}
}
else if((STATE_OUTER == state) && !strcmp(curouter, ptag)) {
/* end of outermost file section */
state = STATE_OUTSIDE;
curouter[0] = '\0';
if(in_wanted_part)
break;
}
}
else if(!in_wanted_part) {
/*
** opening section tag
*/
/* get potential tag */
end = ptr;
EAT_WORD(end);
len.sig = end - ptr;
if(len.sig > MAX_TAG_LEN) {
error = GPE_NO_BUFFER_SPACE;
break;
}
memcpy(ptag, ptr, len.uns);
ptag[len.uns] = '\0';
/* ignore comments, doctypes and xml declarations */
if(('!' == ptag[0]) || ('?' == ptag[0])) {
show(("* ignoring (%s)", buffer));
continue;
}
/* get all potential attributes */
ptr = end;
EAT_SPACE(ptr);
end = ptr;
while(*end && ('>' != *end))
end++;
len.sig = end - ptr;
if(len.sig > MAX_TAG_LEN) {
error = GPE_NO_BUFFER_SPACE;
break;
}
memcpy(patt, ptr, len.uns);
patt[len.uns] = '\0';
if(STATE_OUTSIDE == state) {
/* outermost element (<testcase>) */
strcpy(curouter, ptag);
state = STATE_OUTER;
continue;
}
else if(STATE_OUTER == state) {
/* start of a main section */
strcpy(curmain, ptag);
state = STATE_INMAIN;
continue;
}
else if(STATE_INMAIN == state) {
/* start of a sub section */
strcpy(cursub, ptag);
state = STATE_INSUB;
if(!strcmp(curmain, main) && !strcmp(cursub, sub)) {
/* start of wanted part */
in_wanted_part = 1;
if(strstr(patt, "base64="))
/* bit rough test, but "mostly" functional, */
/* treat wanted part data as base64 encoded */
base64 = 1;
if(strstr(patt, "nonewline=")) {
show(("* setting nonewline\n"));
nonewline = 1;
}
}
continue;
}
}
if(in_wanted_part) {
show(("=> %s", buffer));
error = appenddata(outbuf, outlen, &outalloc, buffer, datalen, base64);
if(error)
break;
}
} /* while */
free(buffer);
if(error != GPE_OK) {
if(error == GPE_END_OF_FILE)
error = GPE_OK;
else {
free(*outbuf);
*outbuf = NULL;
*outlen = 0;
}
}
return error;
}
+891
View File
@@ -0,0 +1,891 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "first.h"
#include <stdlib.h>
#include <string.h>
/* Function
*
* Accepts a TCP connection on a custom port (IPv4 or IPv6). Speaks MQTT.
*
* Read commands from FILE (set with --config). The commands control how to
* act and is reset to defaults each client TCP connect.
*
*/
/* based on sockfilt.c */
#define MQTT_MSG_CONNECT 0x10
#define MQTT_MSG_CONNACK 0x20
#define MQTT_MSG_PUBLISH 0x30
/* #define MQTT_MSG_PUBACK 0x40 */
#define MQTT_MSG_SUBSCRIBE 0x82
#define MQTT_MSG_SUBACK 0x90
#define MQTT_MSG_DISCONNECT 0xe0
struct mqttd_configurable {
unsigned char version; /* initial version byte in the request must match
this */
bool publish_before_suback;
bool short_publish;
bool excessive_remaining;
unsigned char error_connack;
int testnum;
};
#define REQUEST_DUMP "server.input"
#define CONFIG_VERSION 5
static struct mqttd_configurable m_config;
static void mqttd_resetdefaults(void)
{
logmsg("Reset to defaults");
m_config.version = CONFIG_VERSION;
m_config.publish_before_suback = FALSE;
m_config.short_publish = FALSE;
m_config.excessive_remaining = FALSE;
m_config.error_connack = 0;
m_config.testnum = 0;
}
static void mqttd_getconfig(void)
{
FILE *fp = fopen(configfile, FOPEN_READTEXT);
mqttd_resetdefaults();
if(fp) {
char buffer[512];
logmsg("parse config file");
while(fgets(buffer, sizeof(buffer), fp)) {
char key[32];
char value[32];
if(sscanf(buffer, "%31s %31s", key, value) == 2) {
if(!strcmp(key, "version")) {
m_config.version = byteval(value);
logmsg("version [%d] set", m_config.version);
}
else if(!strcmp(key, "PUBLISH-before-SUBACK")) {
logmsg("PUBLISH-before-SUBACK set");
m_config.publish_before_suback = TRUE;
}
else if(!strcmp(key, "short-PUBLISH")) {
logmsg("short-PUBLISH set");
m_config.short_publish = TRUE;
}
else if(!strcmp(key, "error-CONNACK")) {
m_config.error_connack = byteval(value);
logmsg("error-CONNACK = %d", m_config.error_connack);
}
else if(!strcmp(key, "Testnum")) {
m_config.testnum = atoi(value);
logmsg("testnum = %d", m_config.testnum);
}
else if(!strcmp(key, "excessive-remaining")) {
logmsg("excessive-remaining set");
m_config.excessive_remaining = TRUE;
}
}
}
fclose(fp);
}
else {
logmsg("No config file '%s' to read", configfile);
}
}
typedef enum {
FROM_CLIENT,
FROM_SERVER
} mqttdir;
static void logprotocol(mqttdir dir,
const char *prefix, size_t remlen,
FILE *output,
unsigned char *buffer, ssize_t len)
{
char data[12000] = "";
ssize_t i;
unsigned char *ptr = buffer;
char *optr = data;
int left = sizeof(data);
for(i = 0; i < len && (left >= 0); i++) {
snprintf(optr, left, "%02x", ptr[i]);
optr += 2;
left -= 2;
}
fprintf(output, "%s %s %x %s\n",
dir == FROM_CLIENT ? "client" : "server",
prefix, (int)remlen, data);
}
/* return 0 on success */
static int connack(FILE *dump, curl_socket_t fd)
{
unsigned char packet[]={
MQTT_MSG_CONNACK, 0x02,
0x00, 0x00
};
ssize_t rc;
packet[3] = m_config.error_connack;
rc = swrite(fd, (char *)packet, sizeof(packet));
if(rc > 0) {
logmsg("WROTE %zd bytes [CONNACK]", rc);
loghex(packet, rc);
logprotocol(FROM_SERVER, "CONNACK", 2, dump, packet, sizeof(packet));
}
if(rc == sizeof(packet)) {
return 0;
}
return 1;
}
/* return 0 on success */
static int suback(FILE *dump, curl_socket_t fd, unsigned short packetid)
{
unsigned char packet[]={
MQTT_MSG_SUBACK, 0x03,
0, 0, /* filled in below */
0x00
};
ssize_t rc;
packet[2] = (unsigned char)(packetid >> 8);
packet[3] = (unsigned char)(packetid & 0xff);
rc = swrite(fd, (char *)packet, sizeof(packet));
if(rc == sizeof(packet)) {
logmsg("WROTE %zd bytes [SUBACK]", rc);
loghex(packet, rc);
logprotocol(FROM_SERVER, "SUBACK", 3, dump, packet, rc);
return 0;
}
return 1;
}
#ifdef QOS
/* return 0 on success */
static int puback(FILE *dump, curl_socket_t fd, unsigned short packetid)
{
unsigned char packet[]={
MQTT_MSG_PUBACK, 0x00,
0, 0 /* filled in below */
};
ssize_t rc;
packet[2] = (unsigned char)(packetid >> 8);
packet[3] = (unsigned char)(packetid & 0xff);
rc = swrite(fd, (char *)packet, sizeof(packet));
if(rc == sizeof(packet)) {
logmsg("WROTE %zd bytes [PUBACK]", rc);
loghex(packet, rc);
logprotocol(FROM_SERVER, dump, packet, rc);
return 0;
}
logmsg("Failed sending [PUBACK]");
return 1;
}
#endif
/* return 0 on success */
static int disconnect(FILE *dump, curl_socket_t fd)
{
unsigned char packet[]={
MQTT_MSG_DISCONNECT, 0x00,
};
ssize_t rc = swrite(fd, (char *)packet, sizeof(packet));
if(rc == sizeof(packet)) {
logmsg("WROTE %zd bytes [DISCONNECT]", rc);
loghex(packet, rc);
logprotocol(FROM_SERVER, "DISCONNECT", 0, dump, packet, rc);
return 0;
}
logmsg("Failed sending [DISCONNECT]");
return 1;
}
/*
do
encodedByte = X MOD 128
X = X DIV 128
// if there are more data to encode, set the top bit of this byte
if ( X > 0 )
encodedByte = encodedByte OR 128
endif
'output' encodedByte
while ( X > 0 )
*/
/* return number of bytes used */
static size_t encode_length(size_t packetlen,
unsigned char *remlength) /* 4 bytes */
{
size_t bytes = 0;
unsigned char encode;
do {
encode = packetlen % 0x80;
packetlen /= 0x80;
if(packetlen)
encode |= 0x80;
remlength[bytes++] = encode;
if(bytes > 3) {
logmsg("too large packet!");
return 0;
}
} while(packetlen);
return bytes;
}
static size_t decode_length(unsigned char *buffer,
size_t buflen, size_t *lenbytes)
{
size_t len = 0;
size_t mult = 1;
size_t i;
unsigned char encoded = 0x80;
for(i = 0; (i < buflen) && (encoded & 0x80); i++) {
encoded = buffer[i];
len += (encoded & 0x7f) * mult;
mult *= 0x80;
}
if(lenbytes)
*lenbytes = i;
return len;
}
/* return 0 on success */
static int publish(FILE *dump,
curl_socket_t fd, unsigned short packetid,
char *topic, const char *payload, size_t payloadlen)
{
size_t topiclen = strlen(topic);
unsigned char *packet;
size_t payloadindex;
size_t remaininglength = topiclen + 2 + payloadlen;
size_t packetlen;
size_t sendamount;
ssize_t rc;
unsigned char rembuffer[4];
size_t encodedlen;
if(m_config.excessive_remaining) {
/* manually set illegal remaining length */
rembuffer[0] = 0xff;
rembuffer[1] = 0xff;
rembuffer[2] = 0xff;
rembuffer[3] = 0x80; /* maximum allowed here by spec is 0x7f */
encodedlen = 4;
}
else
encodedlen = encode_length(remaininglength, rembuffer);
/* one packet type byte (possibly two more for packetid) */
packetlen = remaininglength + encodedlen + 1;
packet = malloc(packetlen);
if(!packet)
return 1;
packet[0] = MQTT_MSG_PUBLISH;
memcpy(&packet[1], rembuffer, encodedlen);
(void)packetid;
/* packet_id if QoS is set */
packet[1 + encodedlen] = (unsigned char)(topiclen >> 8);
packet[2 + encodedlen] = (unsigned char)(topiclen & 0xff);
memcpy(&packet[3 + encodedlen], topic, topiclen);
payloadindex = 3 + topiclen + encodedlen;
memcpy(&packet[payloadindex], payload, payloadlen);
sendamount = packetlen;
if(m_config.short_publish)
sendamount -= 2;
rc = swrite(fd, (char *)packet, sendamount);
if(rc > 0) {
logmsg("WROTE %zd bytes [PUBLISH]", rc);
loghex(packet, rc);
logprotocol(FROM_SERVER, "PUBLISH", remaininglength, dump, packet, rc);
}
free(packet);
if((size_t)rc == packetlen)
return 0;
return 1;
}
#define MAX_TOPIC_LENGTH 65535
#define MAX_CLIENT_ID_LENGTH 32
static char topic[MAX_TOPIC_LENGTH + 1];
static int fixedheader(curl_socket_t fd,
unsigned char *bytep,
size_t *remaining_lengthp,
size_t *remaining_length_bytesp)
{
/* get the fixed header */
unsigned char buffer[10];
/* get the first two bytes */
ssize_t rc = sread(fd, (char *)buffer, 2);
size_t i;
if(rc < 2) {
logmsg("READ %zd bytes [SHORT!]", rc);
return 1; /* fail */
}
logmsg("READ %zd bytes", rc);
loghex(buffer, rc);
*bytep = buffer[0];
/* if the length byte has the top bit set, get the next one too */
i = 1;
while(buffer[i] & 0x80) {
i++;
rc = sread(fd, (char *)&buffer[i], 1);
if(rc != 1) {
logmsg("Remaining Length broken");
return 1;
}
}
*remaining_lengthp = decode_length(&buffer[1], i, remaining_length_bytesp);
logmsg("Remaining Length: %zu [%zu bytes]", *remaining_lengthp,
*remaining_length_bytesp);
return 0;
}
static curl_socket_t mqttit(curl_socket_t fd)
{
size_t buff_size = 10*1024;
unsigned char *buffer = NULL;
ssize_t rc;
unsigned char byte;
unsigned short packet_id;
size_t payload_len;
size_t client_id_length;
size_t topic_len;
size_t remaining_length = 0;
size_t bytes = 0; /* remaining length field size in bytes */
char client_id[MAX_CLIENT_ID_LENGTH];
long testno;
FILE *stream = NULL;
FILE *dump;
char dumpfile[256];
static const char protocol[7] = {
0x00, 0x04, /* protocol length */
'M','Q','T','T', /* protocol name */
0x04 /* protocol level */
};
snprintf(dumpfile, sizeof(dumpfile), "%s/%s", logdir, REQUEST_DUMP);
dump = fopen(dumpfile, "ab");
if(!dump)
goto end;
mqttd_getconfig();
testno = m_config.testnum;
if(testno)
logmsg("Found test number %ld", testno);
buffer = malloc(buff_size);
if(!buffer) {
logmsg("Out of memory, unable to allocate buffer");
goto end;
}
memset(buffer, 0, buff_size);
do {
unsigned char usr_flag = 0x80;
unsigned char passwd_flag = 0x40;
unsigned char conn_flags;
const size_t client_id_offset = 12;
size_t start_usr;
size_t start_passwd;
/* get the fixed header */
rc = fixedheader(fd, &byte, &remaining_length, &bytes);
if(rc)
break;
if(remaining_length >= buff_size) {
unsigned char *newbuffer;
buff_size = remaining_length;
newbuffer = realloc(buffer, buff_size);
if(!newbuffer) {
logmsg("Failed realloc of size %zu", buff_size);
goto end;
}
buffer = newbuffer;
}
if(remaining_length) {
/* reading variable header and payload into buffer */
rc = sread(fd, (char *)buffer, remaining_length);
if(rc > 0) {
logmsg("READ %zd bytes", rc);
loghex(buffer, rc);
}
}
if(byte == MQTT_MSG_CONNECT) {
logprotocol(FROM_CLIENT, "CONNECT", remaining_length,
dump, buffer, rc);
if(memcmp(protocol, buffer, sizeof(protocol))) {
logmsg("Protocol preamble mismatch");
goto end;
}
/* ignore the connect flag byte and two keepalive bytes */
payload_len = (size_t)(buffer[10] << 8) | buffer[11];
/* first part of the payload is the client ID */
client_id_length = payload_len;
/* checking if user and password flags were set */
conn_flags = buffer[7];
start_usr = client_id_offset + payload_len;
if(usr_flag == (unsigned char)(conn_flags & usr_flag)) {
logmsg("User flag is present in CONN flag");
payload_len += (size_t)(buffer[start_usr] << 8) |
buffer[start_usr + 1];
payload_len += 2; /* MSB and LSB for user length */
}
start_passwd = client_id_offset + payload_len;
if(passwd_flag == (char)(conn_flags & passwd_flag)) {
logmsg("Password flag is present in CONN flags");
payload_len += (size_t)(buffer[start_passwd] << 8) |
buffer[start_passwd + 1];
payload_len += 2; /* MSB and LSB for password length */
}
/* check the length of the payload */
if((ssize_t)payload_len != (rc - 12)) {
logmsg("Payload length mismatch, expected %zx got %zx",
rc - 12, payload_len);
goto end;
}
/* check the length of the client ID */
else if((client_id_length + 1) > MAX_CLIENT_ID_LENGTH) {
logmsg("Too large client id");
goto end;
}
memcpy(client_id, &buffer[12], client_id_length);
client_id[client_id_length] = 0;
logmsg("MQTT client connect accepted: %s", client_id);
/* The first packet sent from the Server to the Client MUST be a
CONNACK Packet */
if(connack(dump, fd)) {
logmsg("failed sending CONNACK");
goto end;
}
}
else if(byte == MQTT_MSG_SUBSCRIBE) {
int error;
char *data;
size_t datalen;
logprotocol(FROM_CLIENT, "SUBSCRIBE", remaining_length,
dump, buffer, rc);
logmsg("Incoming SUBSCRIBE");
if(rc < 6) {
logmsg("Too small SUBSCRIBE");
goto end;
}
/* two bytes packet id */
packet_id = (unsigned short)((buffer[0] << 8) | buffer[1]);
/* two bytes topic length */
topic_len = (size_t)(buffer[2] << 8) | buffer[3];
if(topic_len != (remaining_length - 5)) {
logmsg("Wrong topic length, got %zu expected %zu",
topic_len, remaining_length - 5);
goto end;
}
memcpy(topic, &buffer[4], topic_len);
topic[topic_len] = 0;
/* there's a QoS byte (two bits) after the topic */
logmsg("SUBSCRIBE to '%s' [%d]", topic, packet_id);
stream = test2fopen(testno, logdir);
if(!stream) {
char errbuf[STRERROR_LEN];
error = errno;
logmsg("fopen() failed with error (%d) %s",
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
logmsg("Couldn't open test file %ld", testno);
goto end;
}
error = getpart(&data, &datalen, "reply", "data", stream);
if(!error) {
if(!m_config.publish_before_suback) {
if(suback(dump, fd, packet_id)) {
logmsg("failed sending SUBACK");
free(data);
goto end;
}
}
if(publish(dump, fd, packet_id, topic, data, datalen)) {
logmsg("PUBLISH failed");
free(data);
goto end;
}
free(data);
if(m_config.publish_before_suback) {
if(suback(dump, fd, packet_id)) {
logmsg("failed sending SUBACK");
goto end;
}
}
}
else {
const char *def = "this is random payload yes yes it is";
publish(dump, fd, packet_id, topic, def, strlen(def));
}
disconnect(dump, fd);
}
else if((byte & 0xf0) == (MQTT_MSG_PUBLISH & 0xf0)) {
size_t topiclen;
logmsg("Incoming PUBLISH");
logprotocol(FROM_CLIENT, "PUBLISH", remaining_length,
dump, buffer, rc);
topiclen = (size_t)(buffer[1 + bytes] << 8) | buffer[2 + bytes];
logmsg("Got %zu bytes topic", topiclen);
#ifdef QOS
/* Handle packetid if there is one. Send puback if QoS > 0 */
puback(dump, fd, 0);
#endif
/* expect a disconnect here */
/* get the request */
rc = sread(fd, (char *)&buffer[0], 2);
logmsg("READ %zd bytes [DISCONNECT]", rc);
loghex(buffer, rc);
logprotocol(FROM_CLIENT, "DISCONNECT", 0, dump, buffer, rc);
goto end;
}
else {
/* not supported (yet) */
goto end;
}
} while(1);
end:
if(buffer)
free(buffer);
if(dump)
fclose(dump);
if(stream)
fclose(stream);
return CURL_SOCKET_BAD;
}
/*
sockfdp is a pointer to an established stream or CURL_SOCKET_BAD
if sockfd is CURL_SOCKET_BAD, listendfd is a listening socket we must
accept()
*/
static bool mqttd_incoming(curl_socket_t listenfd)
{
fd_set fds_read;
fd_set fds_write;
fd_set fds_err;
int clients = 0; /* connected clients */
if(got_exit_signal) {
logmsg("signalled to die, exiting...");
return FALSE;
}
#ifdef HAVE_GETPPID
/* As a last resort, quit if socks5 process becomes orphan. */
if(getppid() <= 1) {
logmsg("process becomes orphan, exiting");
return FALSE;
}
#endif
do {
ssize_t rc;
int error = 0;
char errbuf[STRERROR_LEN];
curl_socket_t sockfd = listenfd;
int maxfd = (int)sockfd;
FD_ZERO(&fds_read);
FD_ZERO(&fds_write);
FD_ZERO(&fds_err);
/* there's always a socket to wait for */
#ifdef __DJGPP__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warith-conversion"
#endif
FD_SET(sockfd, &fds_read);
#ifdef __DJGPP__
#pragma GCC diagnostic pop
#endif
do {
/* select() blocking behavior call on blocking descriptors please */
rc = select(maxfd + 1, &fds_read, &fds_write, &fds_err, NULL);
if(got_exit_signal) {
logmsg("signalled to die, exiting...");
return FALSE;
}
} while((rc == -1) && ((error = SOCKERRNO) == SOCKEINTR));
if(rc < 0) {
logmsg("select() failed with error (%d) %s",
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
return FALSE;
}
if(FD_ISSET(sockfd, &fds_read)) {
curl_socket_t newfd = accept(sockfd, NULL, NULL);
if(CURL_SOCKET_BAD == newfd) {
error = SOCKERRNO;
logmsg("accept() failed with error (%d) %s",
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
}
else {
logmsg("====> Client connect, fd %ld. "
"Read config from %s", (long)newfd, configfile);
set_advisor_read_lock(loglockfile);
(void)mqttit(newfd); /* until done */
clear_advisor_read_lock(loglockfile);
logmsg("====> Client disconnect");
sclose(newfd);
}
}
} while(clients);
return TRUE;
}
static int test_mqttd(int argc, char *argv[])
{
curl_socket_t sock = CURL_SOCKET_BAD;
curl_socket_t msgsock = CURL_SOCKET_BAD;
int wrotepidfile = 0;
int wroteportfile = 0;
bool juggle_again;
int error;
char errbuf[STRERROR_LEN];
int arg = 1;
pidname = ".mqttd.pid";
portname = ".mqttd.port";
serverlogfile = "log/mqttd.log";
configfile = "mqttd.config";
server_port = 1883; /* MQTT default port */
while(argc > arg) {
if(!strcmp("--version", argv[arg])) {
printf("mqttd IPv4%s\n",
#ifdef USE_IPV6
"/IPv6"
#else
""
#endif
);
return 0;
}
else if(!strcmp("--pidfile", argv[arg])) {
arg++;
if(argc > arg)
pidname = argv[arg++];
}
else if(!strcmp("--portfile", argv[arg])) {
arg++;
if(argc > arg)
portname = argv[arg++];
}
else if(!strcmp("--config", argv[arg])) {
arg++;
if(argc > arg)
configfile = argv[arg++];
}
else if(!strcmp("--logfile", argv[arg])) {
arg++;
if(argc > arg)
serverlogfile = argv[arg++];
}
else if(!strcmp("--logdir", argv[arg])) {
arg++;
if(argc > arg)
logdir = argv[arg++];
}
else if(!strcmp("--ipv6", argv[arg])) {
#ifdef USE_IPV6
socket_domain = AF_INET6;
ipv_inuse = "IPv6";
#endif
arg++;
}
else if(!strcmp("--ipv4", argv[arg])) {
/* for completeness, we support this option as well */
#ifdef USE_IPV6
socket_domain = AF_INET;
ipv_inuse = "IPv4";
#endif
arg++;
}
else if(!strcmp("--port", argv[arg])) {
arg++;
if(argc > arg) {
int inum = atoi(argv[arg]);
if(inum && ((inum < 1025) || (inum > 65535))) {
fprintf(stderr, "mqttd: invalid --port argument (%s)\n",
argv[arg]);
return 0;
}
server_port = (unsigned short)inum;
arg++;
}
}
else {
puts("Usage: mqttd [option]\n"
" --config [file]\n"
" --version\n"
" --logfile [file]\n"
" --logdir [directory]\n"
" --pidfile [file]\n"
" --portfile [file]\n"
" --ipv4\n"
" --ipv6\n"
" --port [port]\n");
return 0;
}
}
snprintf(loglockfile, sizeof(loglockfile), "%s/%s/mqtt-%s.lock",
logdir, SERVERLOGS_LOCKDIR, ipv_inuse);
#ifdef _WIN32
if(win32_init())
return 2;
#endif
CURLX_SET_BINMODE(stdin);
CURLX_SET_BINMODE(stdout);
CURLX_SET_BINMODE(stderr);
install_signal_handlers(FALSE);
sock = socket(socket_domain, SOCK_STREAM, 0);
if(CURL_SOCKET_BAD == sock) {
error = SOCKERRNO;
logmsg("Error creating socket (%d) %s",
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
goto mqttd_cleanup;
}
{
/* passive daemon style */
sock = sockdaemon(sock, &server_port, NULL, FALSE);
if(CURL_SOCKET_BAD == sock) {
goto mqttd_cleanup;
}
msgsock = CURL_SOCKET_BAD; /* no stream socket yet */
}
logmsg("Running %s version", ipv_inuse);
logmsg("Listening on port %hu", server_port);
wrotepidfile = write_pidfile(pidname);
if(!wrotepidfile) {
goto mqttd_cleanup;
}
wroteportfile = write_portfile(portname, server_port);
if(!wroteportfile) {
goto mqttd_cleanup;
}
do {
juggle_again = mqttd_incoming(sock);
} while(juggle_again);
mqttd_cleanup:
if((msgsock != sock) && (msgsock != CURL_SOCKET_BAD))
sclose(msgsock);
if(sock != CURL_SOCKET_BAD)
sclose(sock);
if(wrotepidfile)
unlink(pidname);
if(wroteportfile)
unlink(portname);
restore_signal_handlers(FALSE);
if(got_exit_signal) {
logmsg("============> mqttd exits with signal (%d)", exit_signal);
/*
* To properly set the return status of the process we
* must raise the same signal SIGINT or SIGTERM that we
* caught and let the old handler take care of it.
*/
raise(exit_signal);
}
logmsg("============> mqttd quits");
return 0;
}
+134
View File
@@ -0,0 +1,134 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "first.h"
/* Purpose
*
* Resolve the given name, using system name resolve functions (NOT any
* function provided by libcurl). Used to see if the name exists and thus if
* we can allow a test case to use it for testing.
*
* Like if 'localhost' actual exists etc.
*
*/
static int test_resolve(int argc, char *argv[])
{
int arg = 1;
const char *host = NULL;
int rc = 0;
while(argc > arg) {
if(!strcmp("--version", argv[arg])) {
printf("resolve IPv4%s\n",
#ifdef CURLRES_IPV6
"/IPv6"
#else
""
#endif
);
return 0;
}
else if(!strcmp("--ipv6", argv[arg])) {
#ifdef CURLRES_IPV6
ipv_inuse = "IPv6";
use_ipv6 = TRUE;
arg++;
#else
puts("IPv6 support has been disabled in this program");
return 1;
#endif
}
else if(!strcmp("--ipv4", argv[arg])) {
/* for completeness, we support this option as well */
ipv_inuse = "IPv4";
#ifdef CURLRES_IPV6
use_ipv6 = FALSE;
#endif
arg++;
}
else {
host = argv[arg++];
}
}
if(!host) {
puts("Usage: resolve [option] <host>\n"
" --version\n"
" --ipv4"
#ifdef CURLRES_IPV6
"\n --ipv6"
#endif
);
return 1;
}
#ifdef _WIN32
if(win32_init())
return 2;
#endif
#ifdef CURLRES_IPV6
if(use_ipv6) {
/* Check that the system has IPv6 enabled before checking the resolver */
curl_socket_t s = socket(PF_INET6, SOCK_DGRAM, 0);
if(s == CURL_SOCKET_BAD)
/* an IPv6 address was requested and we can't get/use one */
rc = -1;
else {
sclose(s);
}
}
if(rc == 0) {
/* getaddrinfo() resolve */
struct addrinfo *ai;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = use_ipv6 ? PF_INET6 : PF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = 0;
rc = getaddrinfo(host, "80", &hints, &ai);
if(rc == 0)
freeaddrinfo(ai);
}
#else
{
struct hostent *he; /* gethostbyname() resolve */
#ifdef __AMIGA__
he = gethostbyname((unsigned char *)CURL_UNCONST(host));
#else
he = gethostbyname(host);
#endif
rc = !he;
}
#endif
if(rc)
printf("Resolving %s '%s' didn't work\n", ipv_inuse, host);
return !!rc;
}
File diff suppressed because it is too large Load Diff
+42
View File
@@ -0,0 +1,42 @@
/* !checksrc! disable COPYRIGHT all */
#include "first.h"
#include "getpart.c"
#include "util.c"
#include "../../lib/curlx/base64.c"
#include "../../lib/curlx/fopen.c"
#include "../../lib/curlx/inet_ntop.c"
#include "../../lib/curlx/inet_pton.c"
#include "../../lib/curlx/multibyte.c"
#include "../../lib/curlx/nonblock.c"
#include "../../lib/curlx/strerr.c"
#include "../../lib/curlx/strparse.c"
#include "../../lib/curlx/timediff.c"
#include "../../lib/curlx/timeval.c"
#include "../../lib/curlx/version_win32.c"
#include "../../lib/curlx/wait.c"
#include "../../lib/curlx/warnless.c"
#include "../../lib/curlx/winapi.c"
#include "dnsd.c"
#include "mqttd.c"
#include "resolve.c"
#include "rtspd.c"
#include "sockfilt.c"
#include "socksd.c"
#include "sws.c"
#include "tftpd.c"
const struct entry_s s_entries[] = {
{"dnsd", test_dnsd},
{"mqttd", test_mqttd},
{"resolve", test_resolve},
{"rtspd", test_rtspd},
{"sockfilt", test_sockfilt},
{"socksd", test_socksd},
{"sws", test_sws},
{"tftpd", test_tftpd},
{NULL, NULL}
};
#include "first.c"
File diff suppressed because it is too large Load Diff
+951
View File
@@ -0,0 +1,951 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "first.h"
#include <stdlib.h>
/* Function
*
* Accepts a TCP connection on a custom port (IPv4 or IPv6). Connects to a
* given addr + port backend (that is NOT extracted form the client's
* request). The backend server default to connect to can be set with
* --backend and --backendport.
*
* Read commands from FILE (set with --config). The commands control how to
* act and is reset to defaults each client TCP connect.
*
* Config file keywords:
*
* "version [number: 5]" - requires the communication to use this version.
* "nmethods_min [number: 1]" - the minimum numberf NMETHODS the client must
* state
* "nmethods_max [number: 3]" - the minimum numberf NMETHODS the client must
* state
* "user [string]" - the user name that must match (if method is 2)
* "password [string]" - the password that must match (if method is 2)
* "backend [IPv4]" - numerical IPv4 address of backend to connect to
* "backendport [number:0]" - TCP port of backend to connect to. 0 means use
the client's specified port number.
* "method [number: 0]" - connect method to respond with:
* 0 - no auth
* 1 - GSSAPI (not supported)
* 2 - user + password
* "response [number]" - the decimal number to respond to a connect
* SOCKS5: 0 is OK, SOCKS4: 90 is ok
*
*/
/* based on sockfilt.c */
static const char *backendaddr = "127.0.0.1";
static unsigned short backendport = 0; /* default is use client's */
struct socksd_configurable {
unsigned char version; /* initial version byte in the request must match
this */
unsigned char nmethods_min; /* minimum number of nmethods to expect */
unsigned char nmethods_max; /* maximum number of nmethods to expect */
unsigned char responseversion;
unsigned char responsemethod;
unsigned char reqcmd;
unsigned char connectrep;
unsigned short port; /* backend port */
char addr[32]; /* backend IPv4 numerical */
char user[256];
char password[256];
};
#define CONFIG_VERSION 5
#define CONFIG_NMETHODS_MIN 1 /* unauth, gssapi, auth */
#define CONFIG_NMETHODS_MAX 3
#define CONFIG_RESPONSEVERSION CONFIG_VERSION
#define CONFIG_RESPONSEMETHOD 0 /* no auth */
#define CONFIG_REQCMD 1 /* CONNECT */
#define CONFIG_PORT backendport
#define CONFIG_ADDR backendaddr
#define CONFIG_CONNECTREP 0
static struct socksd_configurable s_config;
static const char *reqlogfile = "log/socksd-request.log";
static void socksd_resetdefaults(void)
{
logmsg("Reset to defaults");
s_config.version = CONFIG_VERSION;
s_config.nmethods_min = CONFIG_NMETHODS_MIN;
s_config.nmethods_max = CONFIG_NMETHODS_MAX;
s_config.responseversion = CONFIG_RESPONSEVERSION;
s_config.responsemethod = CONFIG_RESPONSEMETHOD;
s_config.reqcmd = CONFIG_REQCMD;
s_config.connectrep = CONFIG_CONNECTREP;
s_config.port = CONFIG_PORT;
strcpy(s_config.addr, CONFIG_ADDR);
strcpy(s_config.user, "user");
strcpy(s_config.password, "password");
}
static unsigned short shortval(char *value)
{
unsigned long num = (unsigned long)atol(value);
return num & 0xffff;
}
static void socksd_getconfig(void)
{
FILE *fp = fopen(configfile, FOPEN_READTEXT);
socksd_resetdefaults();
if(fp) {
char buffer[512];
logmsg("parse config file");
while(fgets(buffer, sizeof(buffer), fp)) {
char key[32];
char value[260];
if(sscanf(buffer, "%31s %259s", key, value) == 2) {
if(!strcmp(key, "version")) {
s_config.version = byteval(value);
logmsg("version [%d] set", s_config.version);
}
else if(!strcmp(key, "nmethods_min")) {
s_config.nmethods_min = byteval(value);
logmsg("nmethods_min [%d] set", s_config.nmethods_min);
}
else if(!strcmp(key, "nmethods_max")) {
s_config.nmethods_max = byteval(value);
logmsg("nmethods_max [%d] set", s_config.nmethods_max);
}
else if(!strcmp(key, "backend")) {
strcpy(s_config.addr, value);
logmsg("backend [%s] set", s_config.addr);
}
else if(!strcmp(key, "backendport")) {
s_config.port = shortval(value);
logmsg("backendport [%d] set", s_config.port);
}
else if(!strcmp(key, "user")) {
strcpy(s_config.user, value);
logmsg("user [%s] set", s_config.user);
}
else if(!strcmp(key, "password")) {
strcpy(s_config.password, value);
logmsg("password [%s] set", s_config.password);
}
/* Methods:
o X'00' NO AUTHENTICATION REQUIRED
o X'01' GSSAPI
o X'02' USERNAME/PASSWORD
*/
else if(!strcmp(key, "method")) {
s_config.responsemethod = byteval(value);
logmsg("method [%d] set", s_config.responsemethod);
}
else if(!strcmp(key, "response")) {
s_config.connectrep = byteval(value);
logmsg("response [%d] set", s_config.connectrep);
}
}
}
fclose(fp);
}
}
/* RFC 1928, SOCKS5 byte index */
#define SOCKS5_VERSION 0
#define SOCKS5_NMETHODS 1 /* number of methods that is listed */
/* in the request: */
#define SOCKS5_REQCMD 1
#define SOCKS5_RESERVED 2
#define SOCKS5_ATYP 3
#define SOCKS5_DSTADDR 4
/* connect response */
#define SOCKS5_REP 1
#define SOCKS5_BNDADDR 4
/* auth request */
#define SOCKS5_ULEN 1
#define SOCKS5_UNAME 2
#define SOCKS4_CD 1
#define SOCKS4_DSTPORT 2
/* connect to a given IPv4 address, not the one asked for */
static curl_socket_t socksconnect(unsigned short connectport,
const char *connectaddr)
{
int rc;
srvr_sockaddr_union_t me;
curl_socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == CURL_SOCKET_BAD)
return CURL_SOCKET_BAD;
memset(&me.sa4, 0, sizeof(me.sa4));
me.sa4.sin_family = AF_INET;
me.sa4.sin_port = htons(connectport);
me.sa4.sin_addr.s_addr = INADDR_ANY;
curlx_inet_pton(AF_INET, connectaddr, &me.sa4.sin_addr);
rc = connect(sock, &me.sa, sizeof(me.sa4));
if(rc) {
char errbuf[STRERROR_LEN];
int error = SOCKERRNO;
logmsg("Failed connecting to %s:%hu (%d) %s", connectaddr, connectport,
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
return CURL_SOCKET_BAD;
}
logmsg("Connected fine to %s:%d", connectaddr, connectport);
return sock;
}
static curl_socket_t socks4(curl_socket_t fd,
unsigned char *buffer,
ssize_t rc)
{
unsigned char response[256 + 16];
curl_socket_t connfd;
unsigned char cd;
unsigned short s4port;
if(buffer[SOCKS4_CD] != 1) {
logmsg("SOCKS4 CD is not 1: %d", buffer[SOCKS4_CD]);
return CURL_SOCKET_BAD;
}
if(rc < 9) {
logmsg("SOCKS4 connect message too short: %zd", rc);
return CURL_SOCKET_BAD;
}
if(!s_config.port)
s4port = (unsigned short)((buffer[SOCKS4_DSTPORT] << 8) |
(buffer[SOCKS4_DSTPORT + 1]));
else
s4port = s_config.port;
connfd = socksconnect(s4port, s_config.addr);
if(connfd == CURL_SOCKET_BAD) {
/* failed */
cd = 91;
}
else {
/* success */
cd = 90;
}
response[0] = 0; /* reply version 0 */
response[1] = cd; /* result */
/* copy port and address from connect request */
memcpy(&response[2], &buffer[SOCKS4_DSTPORT], 6);
rc = (send)(fd, (char *)response, 8, 0);
if(rc != 8) {
logmsg("Sending SOCKS4 response failed!");
return CURL_SOCKET_BAD;
}
logmsg("Sent %zd bytes", rc);
loghex(response, rc);
if(cd == 90)
/* now do the transfer */
return connfd;
if(connfd != CURL_SOCKET_BAD)
sclose(connfd);
return CURL_SOCKET_BAD;
}
static curl_socket_t sockit(curl_socket_t fd)
{
unsigned char buffer[2*256 + 16];
unsigned char response[2*256 + 16];
ssize_t rc;
unsigned char len;
unsigned char type;
unsigned char rep = 0;
unsigned char *address;
unsigned short socksport;
curl_socket_t connfd = CURL_SOCKET_BAD;
unsigned short s5port;
socksd_getconfig();
rc = recv(fd, (char *)buffer, sizeof(buffer), 0);
if(rc <= 0) {
logmsg("SOCKS identifier message missing, recv returned %zd", rc);
return CURL_SOCKET_BAD;
}
logmsg("READ %zd bytes", rc);
loghex(buffer, rc);
if(buffer[SOCKS5_VERSION] == 4)
return socks4(fd, buffer, rc);
if(rc < 3) {
logmsg("SOCKS5 identifier message too short: %zd", rc);
return CURL_SOCKET_BAD;
}
if(buffer[SOCKS5_VERSION] != s_config.version) {
logmsg("VERSION byte not %d", s_config.version);
return CURL_SOCKET_BAD;
}
if((buffer[SOCKS5_NMETHODS] < s_config.nmethods_min) ||
(buffer[SOCKS5_NMETHODS] > s_config.nmethods_max)) {
logmsg("NMETHODS byte not within %d - %d ",
s_config.nmethods_min, s_config.nmethods_max);
return CURL_SOCKET_BAD;
}
/* after NMETHODS follows that many bytes listing the methods the client
says it supports */
if(rc != (buffer[SOCKS5_NMETHODS] + 2)) {
logmsg("Expected %d bytes, got %zd", buffer[SOCKS5_NMETHODS] + 2, rc);
return CURL_SOCKET_BAD;
}
logmsg("Incoming request deemed fine!");
/* respond with two bytes: VERSION + METHOD */
response[0] = s_config.responseversion;
response[1] = s_config.responsemethod;
rc = (send)(fd, (char *)response, 2, 0);
if(rc != 2) {
logmsg("Sending response failed!");
return CURL_SOCKET_BAD;
}
logmsg("Sent %zd bytes", rc);
loghex(response, rc);
/* expect the request or auth */
rc = recv(fd, (char *)buffer, sizeof(buffer), 0);
if(rc <= 0) {
logmsg("SOCKS5 request or auth message missing, recv returned %zd", rc);
return CURL_SOCKET_BAD;
}
logmsg("READ %zd bytes", rc);
loghex(buffer, rc);
if(s_config.responsemethod == 2) {
/* RFC 1929 authentication
+----+------+----------+------+----------+
|VER | ULEN | UNAME | PLEN | PASSWD |
+----+------+----------+------+----------+
| 1 | 1 | 1 to 255 | 1 | 1 to 255 |
+----+------+----------+------+----------+
*/
unsigned char ulen;
unsigned char plen;
bool login = TRUE;
if(rc < 5) {
logmsg("Too short auth input: %zd", rc);
return CURL_SOCKET_BAD;
}
if(buffer[SOCKS5_VERSION] != 1) {
logmsg("Auth VERSION byte not 1, got %d", buffer[SOCKS5_VERSION]);
return CURL_SOCKET_BAD;
}
ulen = buffer[SOCKS5_ULEN];
if(rc < 4 + ulen) {
logmsg("Too short packet for username: %zd", rc);
return CURL_SOCKET_BAD;
}
plen = buffer[SOCKS5_ULEN + ulen + 1];
if(rc < 3 + ulen + plen) {
logmsg("Too short packet for ulen %d plen %d: %zd", ulen, plen, rc);
return CURL_SOCKET_BAD;
}
if((ulen != strlen(s_config.user)) ||
(plen != strlen(s_config.password)) ||
memcmp(&buffer[SOCKS5_UNAME], s_config.user, ulen) ||
memcmp(&buffer[SOCKS5_UNAME + ulen + 1], s_config.password, plen)) {
/* no match! */
logmsg("mismatched credentials!");
login = FALSE;
}
response[0] = 1;
response[1] = login ? 0 : 1;
rc = (send)(fd, (char *)response, 2, 0);
if(rc != 2) {
logmsg("Sending auth response failed!");
return CURL_SOCKET_BAD;
}
logmsg("Sent %zd bytes", rc);
loghex(response, rc);
if(!login)
return CURL_SOCKET_BAD;
/* expect the request */
rc = recv(fd, (char *)buffer, sizeof(buffer), 0);
if(rc <= 0) {
logmsg("SOCKS5 request message missing, recv returned %zd", rc);
return CURL_SOCKET_BAD;
}
logmsg("READ %zd bytes", rc);
loghex(buffer, rc);
}
if(rc < 6) {
logmsg("Too short for request: %zd", rc);
return CURL_SOCKET_BAD;
}
if(buffer[SOCKS5_VERSION] != s_config.version) {
logmsg("Request VERSION byte not %d", s_config.version);
return CURL_SOCKET_BAD;
}
/* 1 == CONNECT */
if(buffer[SOCKS5_REQCMD] != s_config.reqcmd) {
logmsg("Request COMMAND byte not %d", s_config.reqcmd);
return CURL_SOCKET_BAD;
}
/* reserved, should be zero */
if(buffer[SOCKS5_RESERVED]) {
logmsg("Request COMMAND byte not %d", s_config.reqcmd);
return CURL_SOCKET_BAD;
}
/* ATYP:
o IP V4 address: X'01'
o DOMAINNAME: X'03'
o IP V6 address: X'04'
*/
type = buffer[SOCKS5_ATYP];
address = &buffer[SOCKS5_DSTADDR];
switch(type) {
case 1:
/* 4 bytes IPv4 address */
len = 4;
break;
case 3:
/* The first octet of the address field contains the number of octets of
name that follow */
len = buffer[SOCKS5_DSTADDR];
len++;
break;
case 4:
/* 16 bytes IPv6 address */
len = 16;
break;
default:
logmsg("Unknown ATYP %d", type);
return CURL_SOCKET_BAD;
}
if(rc < (4 + len + 2)) {
logmsg("Request too short: %zd, expected %d", rc, 4 + len + 2);
return CURL_SOCKET_BAD;
}
logmsg("Received ATYP %d", type);
{
FILE *dump;
dump = fopen(reqlogfile, "ab");
if(dump) {
int i;
fprintf(dump, "atyp %u =>", type);
switch(type) {
case 1:
/* 4 bytes IPv4 address */
fprintf(dump, " %u.%u.%u.%u\n",
address[0], address[1], address[2], address[3]);
break;
case 3:
/* The first octet of the address field contains the number of octets
of name that follow */
fprintf(dump, " %.*s\n", len-1, &address[1]);
break;
case 4:
/* 16 bytes IPv6 address */
for(i = 0; i < 16; i++) {
fprintf(dump, " %02x", address[i]);
}
fprintf(dump, "\n");
break;
}
fclose(dump);
}
}
if(!s_config.port) {
unsigned char *portp = &buffer[SOCKS5_DSTADDR + len];
s5port = (unsigned short)((portp[0] << 8) | (portp[1]));
}
else
s5port = s_config.port;
if(!s_config.connectrep)
connfd = socksconnect(s5port, s_config.addr);
if(connfd == CURL_SOCKET_BAD) {
/* failed */
rep = 1;
}
else {
rep = s_config.connectrep;
}
/* */
response[SOCKS5_VERSION] = s_config.responseversion;
/*
o REP Reply field:
o X'00' succeeded
o X'01' general SOCKS server failure
o X'02' connection not allowed by ruleset
o X'03' Network unreachable
o X'04' Host unreachable
o X'05' Connection refused
o X'06' TTL expired
o X'07' Command not supported
o X'08' Address type not supported
o X'09' to X'FF' unassigned
*/
response[SOCKS5_REP] = rep;
response[SOCKS5_RESERVED] = 0; /* must be zero */
response[SOCKS5_ATYP] = type; /* address type */
/* mirror back the original addr + port */
/* address or hostname */
memcpy(&response[SOCKS5_BNDADDR], address, len);
/* port number */
memcpy(&response[SOCKS5_BNDADDR + len],
&buffer[SOCKS5_DSTADDR + len], sizeof(socksport));
rc = (send)(fd, (char *)response, (SEND_TYPE_ARG3)(len + 6), 0);
if(rc != (len + 6)) {
logmsg("Sending connect response failed!");
return CURL_SOCKET_BAD;
}
logmsg("Sent %zd bytes", rc);
loghex(response, rc);
if(!rep)
return connfd;
if(connfd != CURL_SOCKET_BAD)
sclose(connfd);
return CURL_SOCKET_BAD;
}
struct perclient {
size_t fromremote;
size_t fromclient;
curl_socket_t remotefd;
curl_socket_t clientfd;
bool used;
};
/* return non-zero when transfer is done */
static int tunnel(struct perclient *cp, fd_set *fds)
{
ssize_t nread;
ssize_t nwrite;
char buffer[512];
if(FD_ISSET(cp->clientfd, fds)) {
/* read from client, send to remote */
nread = recv(cp->clientfd, buffer, sizeof(buffer), 0);
if(nread > 0) {
nwrite = send(cp->remotefd, (char *)buffer,
(SEND_TYPE_ARG3)nread, 0);
if(nwrite != nread)
return 1;
cp->fromclient += nwrite;
}
else
return 1;
}
if(FD_ISSET(cp->remotefd, fds)) {
/* read from remote, send to client */
nread = recv(cp->remotefd, buffer, sizeof(buffer), 0);
if(nread > 0) {
nwrite = send(cp->clientfd, (char *)buffer,
(SEND_TYPE_ARG3)nread, 0);
if(nwrite != nread)
return 1;
cp->fromremote += nwrite;
}
else
return 1;
}
return 0;
}
/*
sockfdp is a pointer to an established stream or CURL_SOCKET_BAD
if sockfd is CURL_SOCKET_BAD, listendfd is a listening socket we must
accept()
*/
static bool socksd_incoming(curl_socket_t listenfd)
{
fd_set fds_read;
fd_set fds_write;
fd_set fds_err;
int clients = 0; /* connected clients */
struct perclient c[2];
memset(c, 0, sizeof(c));
if(got_exit_signal) {
logmsg("signalled to die, exiting...");
return FALSE;
}
#ifdef HAVE_GETPPID
/* As a last resort, quit if socks5 process becomes orphan. */
if(getppid() <= 1) {
logmsg("process becomes orphan, exiting");
return FALSE;
}
#endif
do {
int i;
ssize_t rc;
int error = 0;
char errbuf[STRERROR_LEN];
curl_socket_t sockfd = listenfd;
int maxfd = (int)sockfd;
FD_ZERO(&fds_read);
FD_ZERO(&fds_write);
FD_ZERO(&fds_err);
/* there's always a socket to wait for */
#ifdef __DJGPP__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warith-conversion"
#endif
FD_SET(sockfd, &fds_read);
#ifdef __DJGPP__
#pragma GCC diagnostic pop
#endif
for(i = 0; i < 2; i++) {
if(c[i].used) {
curl_socket_t fd = c[i].clientfd;
#ifdef __DJGPP__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warith-conversion"
#endif
FD_SET(fd, &fds_read);
#ifdef __DJGPP__
#pragma GCC diagnostic pop
#endif
if((int)fd > maxfd)
maxfd = (int)fd;
fd = c[i].remotefd;
#ifdef __DJGPP__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warith-conversion"
#endif
FD_SET(fd, &fds_read);
#ifdef __DJGPP__
#pragma GCC diagnostic pop
#endif
if((int)fd > maxfd)
maxfd = (int)fd;
}
}
do {
/* select() blocking behavior call on blocking descriptors please */
rc = select(maxfd + 1, &fds_read, &fds_write, &fds_err, NULL);
if(got_exit_signal) {
logmsg("signalled to die, exiting...");
return FALSE;
}
} while((rc == -1) && ((error = SOCKERRNO) == SOCKEINTR));
if(rc < 0) {
logmsg("select() failed with error (%d) %s",
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
return FALSE;
}
if((clients < 2) && FD_ISSET(sockfd, &fds_read)) {
curl_socket_t newfd = accept(sockfd, NULL, NULL);
if(CURL_SOCKET_BAD == newfd) {
error = SOCKERRNO;
logmsg("accept() failed with error (%d) %s",
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
}
else {
curl_socket_t remotefd;
logmsg("====> Client connect, "
"Read config from %s", configfile);
remotefd = sockit(newfd); /* SOCKS until done */
if(remotefd == CURL_SOCKET_BAD) {
logmsg("====> Client disconnect");
sclose(newfd);
}
else {
struct perclient *cp = &c[0];
logmsg("====> Tunnel transfer");
if(c[0].used)
cp = &c[1];
cp->fromremote = 0;
cp->fromclient = 0;
cp->clientfd = newfd;
cp->remotefd = remotefd;
cp->used = TRUE;
clients++;
}
}
}
for(i = 0; i < 2; i++) {
struct perclient *cp = &c[i];
if(cp->used) {
if(tunnel(cp, &fds_read)) {
logmsg("SOCKS transfer completed. Bytes: < %zu > %zu",
cp->fromremote, cp->fromclient);
sclose(cp->clientfd);
sclose(cp->remotefd);
cp->used = FALSE;
clients--;
}
}
}
} while(clients);
return TRUE;
}
static int test_socksd(int argc, char *argv[])
{
curl_socket_t sock = CURL_SOCKET_BAD;
curl_socket_t msgsock = CURL_SOCKET_BAD;
int wrotepidfile = 0;
int wroteportfile = 0;
bool juggle_again;
int error;
char errbuf[STRERROR_LEN];
int arg = 1;
const char *unix_socket = NULL;
#ifdef USE_UNIX_SOCKETS
bool unlink_socket = false;
#endif
pidname = ".socksd.pid";
serverlogfile = "log/socksd.log";
configfile = "socksd.config";
server_port = 8905;
while(argc > arg) {
if(!strcmp("--version", argv[arg])) {
printf("socksd IPv4%s\n",
#ifdef USE_IPV6
"/IPv6"
#else
""
#endif
);
return 0;
}
else if(!strcmp("--pidfile", argv[arg])) {
arg++;
if(argc > arg)
pidname = argv[arg++];
}
else if(!strcmp("--portfile", argv[arg])) {
arg++;
if(argc > arg)
portname = argv[arg++];
}
else if(!strcmp("--config", argv[arg])) {
arg++;
if(argc > arg)
configfile = argv[arg++];
}
else if(!strcmp("--backend", argv[arg])) {
arg++;
if(argc > arg)
backendaddr = argv[arg++];
}
else if(!strcmp("--backendport", argv[arg])) {
arg++;
if(argc > arg)
backendport = (unsigned short)atoi(argv[arg++]);
}
else if(!strcmp("--logfile", argv[arg])) {
arg++;
if(argc > arg)
serverlogfile = argv[arg++];
}
else if(!strcmp("--reqfile", argv[arg])) {
arg++;
if(argc > arg)
reqlogfile = argv[arg++];
}
else if(!strcmp("--ipv6", argv[arg])) {
#ifdef USE_IPV6
socket_domain = AF_INET6;
socket_type = "IPv6";
#endif
arg++;
}
else if(!strcmp("--ipv4", argv[arg])) {
/* for completeness, we support this option as well */
#ifdef USE_IPV6
socket_type = "IPv4";
#endif
arg++;
}
else if(!strcmp("--unix-socket", argv[arg])) {
arg++;
if(argc > arg) {
#ifdef USE_UNIX_SOCKETS
struct sockaddr_un sau;
unix_socket = argv[arg];
if(strlen(unix_socket) >= sizeof(sau.sun_path)) {
fprintf(stderr,
"socksd: socket path must be shorter than %u chars: %s\n",
(unsigned int)sizeof(sau.sun_path), unix_socket);
return 0;
}
socket_domain = AF_UNIX;
socket_type = "unix";
#endif
arg++;
}
}
else if(!strcmp("--port", argv[arg])) {
arg++;
if(argc > arg) {
server_port = (unsigned short)atol(argv[arg]);
arg++;
}
}
else {
puts("Usage: socksd [option]\n"
" --backend [ipv4 addr]\n"
" --backendport [TCP port]\n"
" --config [file]\n"
" --version\n"
" --logfile [file]\n"
" --pidfile [file]\n"
" --portfile [file]\n"
" --reqfile [file]\n"
" --ipv4\n"
" --ipv6\n"
" --unix-socket [file]\n"
" --port [port]\n");
return 0;
}
}
#ifdef _WIN32
if(win32_init())
return 2;
#endif
CURLX_SET_BINMODE(stdin);
CURLX_SET_BINMODE(stdout);
CURLX_SET_BINMODE(stderr);
install_signal_handlers(false);
sock = socket(socket_domain, SOCK_STREAM, 0);
if(CURL_SOCKET_BAD == sock) {
error = SOCKERRNO;
logmsg("Error creating socket (%d) %s",
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
goto socks5_cleanup;
}
{
/* passive daemon style */
sock = sockdaemon(sock, &server_port, unix_socket, FALSE);
if(CURL_SOCKET_BAD == sock) {
goto socks5_cleanup;
}
#ifdef USE_UNIX_SOCKETS
unlink_socket = true;
#endif
msgsock = CURL_SOCKET_BAD; /* no stream socket yet */
}
logmsg("Running %s version", socket_type);
#ifdef USE_UNIX_SOCKETS
if(socket_domain == AF_UNIX)
logmsg("Listening on Unix socket %s", unix_socket);
else
#endif
logmsg("Listening on port %hu", server_port);
wrotepidfile = write_pidfile(pidname);
if(!wrotepidfile) {
goto socks5_cleanup;
}
if(portname) {
wroteportfile = write_portfile(portname, server_port);
if(!wroteportfile) {
goto socks5_cleanup;
}
}
do {
juggle_again = socksd_incoming(sock);
} while(juggle_again);
socks5_cleanup:
if((msgsock != sock) && (msgsock != CURL_SOCKET_BAD))
sclose(msgsock);
if(sock != CURL_SOCKET_BAD)
sclose(sock);
#ifdef USE_UNIX_SOCKETS
if(unlink_socket && socket_domain == AF_UNIX && unix_socket) {
error = unlink(unix_socket);
logmsg("unlink(%s) = %d (%s)", unix_socket,
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
}
#endif
if(wrotepidfile)
unlink(pidname);
if(wroteportfile)
unlink(portname);
restore_signal_handlers(false);
if(got_exit_signal) {
logmsg("============> socksd exits with signal (%d)", exit_signal);
/*
* To properly set the return status of the process we
* must raise the same signal SIGINT or SIGTERM that we
* caught and let the old handler take care of it.
*/
raise(exit_signal);
}
logmsg("============> socksd quits");
return 0;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+922
View File
@@ -0,0 +1,922 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "first.h"
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
/* This function returns a pointer to STATIC memory. It converts the given
* binary lump to a hex formatted string usable for output in logs or
* whatever.
*/
char *data_to_hex(char *data, size_t len)
{
static char buf[256*3];
size_t i;
char *optr = buf;
char *iptr = data;
if(len > 255)
len = 255;
for(i = 0; i < len; i++) {
if((data[i] >= 0x20) && (data[i] < 0x7f))
*optr++ = *iptr++;
else {
snprintf(optr, 4, "%%%02x", (unsigned char)*iptr++);
optr += 3;
}
}
*optr = 0; /* in case no sprintf was used */
return buf;
}
void loghex(unsigned char *buffer, ssize_t len)
{
char data[12000];
ssize_t i;
unsigned char *ptr = buffer;
char *optr = data;
ssize_t width = 0;
int left = sizeof(data);
for(i = 0; i < len && (left >= 0); i++) {
snprintf(optr, left, "%02x", ptr[i]);
width += 2;
optr += 2;
left -= 2;
}
if(width)
logmsg("'%s'", data);
}
void logmsg(const char *msg, ...)
{
va_list ap;
char buffer[2048 + 1];
FILE *logfp;
struct curltime tv;
time_t sec;
struct tm *now;
char timebuf[50];
static time_t epoch_offset;
static int known_offset;
if(!serverlogfile) {
fprintf(stderr, "Serverlogfile not set error\n");
return;
}
tv = curlx_now();
if(!known_offset) {
epoch_offset = time(NULL) - tv.tv_sec;
known_offset = 1;
}
sec = epoch_offset + tv.tv_sec;
/* !checksrc! disable BANNEDFUNC 1 */
now = localtime(&sec); /* not thread safe but we don't care */
snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d.%06ld",
(int)now->tm_hour, (int)now->tm_min, (int)now->tm_sec,
(long)tv.tv_usec);
va_start(ap, msg);
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-nonliteral"
#endif
vsnprintf(buffer, sizeof(buffer), msg, ap);
#ifdef __clang__
#pragma clang diagnostic pop
#endif
va_end(ap);
do {
logfp = fopen(serverlogfile, "ab");
/* !checksrc! disable ERRNOVAR 1 */
} while(!logfp && (errno == EINTR));
if(logfp) {
fprintf(logfp, "%s %s\n", timebuf, buffer);
fclose(logfp);
}
else {
char errbuf[STRERROR_LEN];
int error = errno;
fprintf(stderr, "fopen() failed with error (%d) %s\n",
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
fprintf(stderr, "Error opening file '%s'\n", serverlogfile);
fprintf(stderr, "Msg not logged: %s %s\n", timebuf, buffer);
}
}
unsigned char byteval(char *value)
{
unsigned int num = (unsigned int)atoi(value);
return num & 0xff;
}
#ifdef _WIN32
/* use instead of perror() on generic Windows */
static void win32_perror(const char *msg)
{
char buf[512];
int err = SOCKERRNO;
curlx_winapi_strerror(err, buf, sizeof(buf));
if(msg)
fprintf(stderr, "%s: ", msg);
fprintf(stderr, "%s\n", buf);
}
static void win32_cleanup(void)
{
#ifdef USE_WINSOCK
WSACleanup();
#endif
/* flush buffers of all streams regardless of their mode */
_flushall();
}
int win32_init(void)
{
curlx_now_init();
#ifdef USE_WINSOCK
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if(err) {
win32_perror("Winsock init failed");
logmsg("Error initialising Winsock -- aborting");
return 1;
}
if(LOBYTE(wsaData.wVersion) != LOBYTE(wVersionRequested) ||
HIBYTE(wsaData.wVersion) != HIBYTE(wVersionRequested) ) {
WSACleanup();
win32_perror("Winsock init failed");
logmsg("No suitable winsock.dll found -- aborting");
return 1;
}
}
#endif /* USE_WINSOCK */
atexit(win32_cleanup);
return 0;
}
#endif /* _WIN32 */
/* fopens the test case file */
FILE *test2fopen(long testno, const char *logdir2)
{
FILE *stream;
char filename[256];
/* first try the alternative, preprocessed, file */
snprintf(filename, sizeof(filename), "%s/test%ld", logdir2, testno);
stream = fopen(filename, "rb");
if(stream)
return stream;
/* then try the source version */
snprintf(filename, sizeof(filename), "%s/data/test%ld", srcpath, testno);
stream = fopen(filename, "rb");
return stream;
}
#ifdef _WIN32
#define t_getpid() GetCurrentProcessId()
#else
#define t_getpid() getpid()
#endif
curl_off_t our_getpid(void)
{
curl_off_t pid = (curl_off_t)t_getpid();
#ifdef _WIN32
/* store pid + MAX_PID to avoid conflict with Cygwin/msys PIDs, see also:
* - 2019-01-31: https://cygwin.com/git/?p=newlib-cygwin.git;a=commit;h=b5e1003722cb14235c4f166be72c09acdffc62ea
* - 2019-02-02: https://cygwin.com/git/?p=newlib-cygwin.git;a=commit;h=448cf5aa4b429d5a9cebf92a0da4ab4b5b6d23fe
* - 2024-12-19: https://cygwin.com/git/?p=newlib-cygwin.git;a=commit;h=363357c023ce01e936bdaedf0f479292a8fa4e0f
*/
pid += 4194304;
#endif
return pid;
}
int write_pidfile(const char *filename)
{
FILE *pidfile;
curl_off_t pid;
pid = our_getpid();
pidfile = fopen(filename, "wb");
if(!pidfile) {
char errbuf[STRERROR_LEN];
logmsg("Couldn't write pid file: %s (%d) %s", filename,
errno, curlx_strerror(errno, errbuf, sizeof(errbuf)));
return 0; /* fail */
}
fprintf(pidfile, "%ld\n", (long)pid);
fclose(pidfile);
logmsg("Wrote pid %ld to %s", (long)pid, filename);
return 1; /* success */
}
/* store the used port number in a file */
int write_portfile(const char *filename, int port)
{
FILE *portfile = fopen(filename, "wb");
if(!portfile) {
char errbuf[STRERROR_LEN];
logmsg("Couldn't write port file: %s (%d) %s", filename,
errno, curlx_strerror(errno, errbuf, sizeof(errbuf)));
return 0; /* fail */
}
fprintf(portfile, "%d\n", port);
fclose(portfile);
logmsg("Wrote port %d to %s", port, filename);
return 1; /* success */
}
void set_advisor_read_lock(const char *filename)
{
FILE *lockfile;
int error = 0;
char errbuf[STRERROR_LEN];
int res;
do {
lockfile = fopen(filename, "wb");
/* !checksrc! disable ERRNOVAR 1 */
} while(!lockfile && ((error = errno) == EINTR));
if(!lockfile) {
logmsg("Error creating lock file %s error (%d) %s", filename,
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
return;
}
res = fclose(lockfile);
if(res)
logmsg("Error closing lock file %s error (%d) %s", filename,
errno, curlx_strerror(errno, errbuf, sizeof(errbuf)));
}
void clear_advisor_read_lock(const char *filename)
{
int error = 0;
int res;
/*
** Log all removal failures. Even those due to file not existing.
** This allows to detect if unexpectedly the file has already been
** removed by a process different than the one that should do this.
*/
do {
res = unlink(filename);
/* !checksrc! disable ERRNOVAR 1 */
} while(res && ((error = errno) == EINTR));
if(res) {
char errbuf[STRERROR_LEN];
logmsg("Error removing lock file %s error (%d) %s", filename,
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
}
}
/* vars used to keep around previous signal handlers */
typedef void (*SIGHANDLER_T)(int);
#if defined(_MSC_VER) && (_MSC_VER <= 1700)
/* Workaround for warning C4306:
'type cast' : conversion from 'int' to 'void (__cdecl *)(int)' */
#undef SIG_ERR
#define SIG_ERR ((SIGHANDLER_T)(size_t)-1)
#endif
#ifdef SIGHUP
static SIGHANDLER_T old_sighup_handler = SIG_ERR;
#endif
#ifdef SIGPIPE
static SIGHANDLER_T old_sigpipe_handler = SIG_ERR;
#endif
#ifdef SIGALRM
static SIGHANDLER_T old_sigalrm_handler = SIG_ERR;
#endif
#ifdef SIGINT
static SIGHANDLER_T old_sigint_handler = SIG_ERR;
#endif
#ifdef SIGTERM
static SIGHANDLER_T old_sigterm_handler = SIG_ERR;
#endif
#if defined(SIGBREAK) && defined(_WIN32)
static SIGHANDLER_T old_sigbreak_handler = SIG_ERR;
#endif
#if defined(_WIN32) && !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)
static DWORD thread_main_id = 0;
static HANDLE thread_main_window = NULL;
static HWND hidden_main_window = NULL;
#endif
/* signal handler that will be triggered to indicate that the program
* should finish its execution in a controlled manner as soon as possible.
* The first time this is called it will set got_exit_signal to one and
* store in exit_signal the signal that triggered its execution.
*/
#ifndef UNDER_CE
/*
* Only call signal-safe functions from the signal handler, as required by
* the POSIX specification:
* https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html
* Hence, do not call 'logmsg()', and instead use 'open/write/close' to
* log errors.
*/
static void exit_signal_handler(int signum)
{
int old_errno = errno;
if(!serverlogfile) {
static const char msg[] = "exit_signal_handler: serverlogfile not set\n";
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
}
else {
#ifdef _WIN32
#define OPENMODE S_IREAD | S_IWRITE
#else
#define OPENMODE S_IRUSR | S_IWUSR
#endif
int fd = open(serverlogfile, O_WRONLY | O_CREAT | O_APPEND, OPENMODE);
if(fd != -1) {
static const char msg[] = "exit_signal_handler: called\n";
(void)!write(fd, msg, sizeof(msg) - 1);
close(fd);
}
else {
static const char msg[] = "exit_signal_handler: failed opening ";
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
(void)!write(STDERR_FILENO, serverlogfile, strlen(serverlogfile));
(void)!write(STDERR_FILENO, "\n", 1);
}
}
if(got_exit_signal == 0) {
got_exit_signal = 1;
exit_signal = signum;
#ifdef _WIN32
if(exit_event)
(void)SetEvent(exit_event);
#endif
}
(void)signal(signum, exit_signal_handler);
CURL_SETERRNO(old_errno);
}
#endif
#if defined(_WIN32) && !defined(UNDER_CE)
/* CTRL event handler for Windows Console applications to simulate
* SIGINT, SIGTERM and SIGBREAK on CTRL events and trigger signal handler.
*
* Background information from MSDN:
* SIGINT is not supported for any Win32 application. When a CTRL+C
* interrupt occurs, Win32 operating systems generate a new thread
* to specifically handle that interrupt. This can cause a single-thread
* application, such as one in UNIX, to become multithreaded and cause
* unexpected behavior.
* [...]
* The SIGKILL and SIGTERM signals are not generated under Windows.
* They are included for ANSI compatibility. Therefore, you can set
* signal handlers for these signals by using signal, and you can also
* explicitly generate these signals by calling raise. Source:
* https://learn.microsoft.com/cpp/c-runtime-library/reference/signal
*/
static BOOL WINAPI ctrl_event_handler(DWORD dwCtrlType)
{
int signum = 0;
logmsg("ctrl_event_handler: %lu", dwCtrlType);
switch(dwCtrlType) {
#ifdef SIGINT
case CTRL_C_EVENT:
signum = SIGINT;
break;
#endif
#ifdef SIGTERM
case CTRL_CLOSE_EVENT:
signum = SIGTERM;
break;
#endif
#ifdef SIGBREAK
case CTRL_BREAK_EVENT:
signum = SIGBREAK;
break;
#endif
default:
return FALSE;
}
if(signum) {
logmsg("ctrl_event_handler: %lu -> %d", dwCtrlType, signum);
raise(signum);
}
return TRUE;
}
#endif
#if defined(_WIN32) && !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)
/* Window message handler for Windows applications to add support
* for graceful process termination via taskkill (without /f) which
* sends WM_CLOSE to all Windows of a process (even hidden ones).
*
* Therefore we create and run a hidden Window in a separate thread
* to receive and handle the WM_CLOSE message as SIGTERM signal.
*/
static LRESULT CALLBACK main_window_proc(HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
int signum = 0;
if(hwnd == hidden_main_window) {
switch(uMsg) {
#ifdef SIGTERM
case WM_CLOSE:
signum = SIGTERM;
break;
#endif
case WM_DESTROY:
PostQuitMessage(0);
break;
}
if(signum) {
logmsg("main_window_proc: %d -> %d", uMsg, signum);
raise(signum);
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
/* Window message queue loop for hidden main window, details see above.
*/
static DWORD WINAPI main_window_loop(void *lpParameter)
{
WNDCLASS wc;
BOOL ret;
MSG msg;
ZeroMemory(&wc, sizeof(wc));
wc.lpfnWndProc = (WNDPROC)main_window_proc;
wc.hInstance = (HINSTANCE)lpParameter;
wc.lpszClassName = TEXT("MainWClass");
if(!RegisterClass(&wc)) {
win32_perror("RegisterClass failed");
return (DWORD)-1;
}
hidden_main_window = CreateWindowEx(0, TEXT("MainWClass"),
TEXT("Recv WM_CLOSE msg"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
(HWND)NULL, (HMENU)NULL,
wc.hInstance, NULL);
if(!hidden_main_window) {
win32_perror("CreateWindowEx failed");
return (DWORD)-1;
}
do {
ret = GetMessage(&msg, NULL, 0, 0);
if(ret == -1) {
win32_perror("GetMessage failed");
return (DWORD)-1;
}
else if(ret) {
if(msg.message == WM_APP) {
DestroyWindow(hidden_main_window);
}
else if(msg.hwnd && !TranslateMessage(&msg)) {
DispatchMessage(&msg);
}
}
} while(ret);
hidden_main_window = NULL;
return (DWORD)msg.wParam;
}
#endif
#ifndef UNDER_CE
static SIGHANDLER_T set_signal(int signum, SIGHANDLER_T handler,
bool restartable)
{
#if defined(HAVE_SIGACTION) && defined(SA_RESTART)
struct sigaction sa, oldsa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, signum);
sa.sa_flags = restartable ? SA_RESTART : 0;
if(sigaction(signum, &sa, &oldsa))
return SIG_ERR;
return oldsa.sa_handler;
#else
SIGHANDLER_T oldhdlr = signal(signum, handler);
#ifdef HAVE_SIGINTERRUPT
if(oldhdlr != SIG_ERR)
siginterrupt(signum, (int) restartable);
#else
(void)restartable;
#endif
return oldhdlr;
#endif
}
#endif
void install_signal_handlers(bool keep_sigalrm)
{
char errbuf[STRERROR_LEN];
(void)errbuf;
#ifdef _WIN32
/* setup Windows exit event before any signal can trigger */
exit_event = CreateEvent(NULL, TRUE, FALSE, NULL);
if(!exit_event)
logmsg("cannot create exit event");
#endif
#ifdef SIGHUP
/* ignore SIGHUP signal */
old_sighup_handler = set_signal(SIGHUP, SIG_IGN, FALSE);
if(old_sighup_handler == SIG_ERR)
logmsg("cannot install SIGHUP handler: (%d) %s",
errno, curlx_strerror(errno, errbuf, sizeof(errbuf)));
#endif
#ifdef SIGPIPE
/* ignore SIGPIPE signal */
old_sigpipe_handler = set_signal(SIGPIPE, SIG_IGN, FALSE);
if(old_sigpipe_handler == SIG_ERR)
logmsg("cannot install SIGPIPE handler: (%d) %s",
errno, curlx_strerror(errno, errbuf, sizeof(errbuf)));
#endif
#ifdef SIGALRM
if(!keep_sigalrm) {
/* ignore SIGALRM signal */
old_sigalrm_handler = set_signal(SIGALRM, SIG_IGN, FALSE);
if(old_sigalrm_handler == SIG_ERR)
logmsg("cannot install SIGALRM handler: (%d) %s",
errno, curlx_strerror(errno, errbuf, sizeof(errbuf)));
}
#else
(void)keep_sigalrm;
#endif
#ifdef SIGINT
/* handle SIGINT signal with our exit_signal_handler */
old_sigint_handler = set_signal(SIGINT, exit_signal_handler, TRUE);
if(old_sigint_handler == SIG_ERR)
logmsg("cannot install SIGINT handler: (%d) %s",
errno, curlx_strerror(errno, errbuf, sizeof(errbuf)));
#endif
#ifdef SIGTERM
/* handle SIGTERM signal with our exit_signal_handler */
old_sigterm_handler = set_signal(SIGTERM, exit_signal_handler, TRUE);
if(old_sigterm_handler == SIG_ERR)
logmsg("cannot install SIGTERM handler: (%d) %s",
errno, curlx_strerror(errno, errbuf, sizeof(errbuf)));
#endif
#if defined(SIGBREAK) && defined(_WIN32)
/* handle SIGBREAK signal with our exit_signal_handler */
old_sigbreak_handler = set_signal(SIGBREAK, exit_signal_handler, TRUE);
if(old_sigbreak_handler == SIG_ERR)
logmsg("cannot install SIGBREAK handler: (%d) %s",
errno, curlx_strerror(errno, errbuf, sizeof(errbuf)));
#endif
#ifdef _WIN32
#ifndef UNDER_CE
if(!SetConsoleCtrlHandler(ctrl_event_handler, TRUE))
logmsg("cannot install CTRL event handler");
#endif
#if !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)
thread_main_window = CreateThread(NULL, 0, &main_window_loop,
GetModuleHandle(NULL), 0, &thread_main_id);
if(!thread_main_window || !thread_main_id)
logmsg("cannot start main window loop");
#endif
#endif
}
void restore_signal_handlers(bool keep_sigalrm)
{
#ifdef SIGHUP
if(SIG_ERR != old_sighup_handler)
(void)set_signal(SIGHUP, old_sighup_handler, FALSE);
#endif
#ifdef SIGPIPE
if(SIG_ERR != old_sigpipe_handler)
(void)set_signal(SIGPIPE, old_sigpipe_handler, FALSE);
#endif
#ifdef SIGALRM
if(!keep_sigalrm) {
if(SIG_ERR != old_sigalrm_handler)
(void)set_signal(SIGALRM, old_sigalrm_handler, FALSE);
}
#else
(void)keep_sigalrm;
#endif
#ifdef SIGINT
if(SIG_ERR != old_sigint_handler)
(void)set_signal(SIGINT, old_sigint_handler, FALSE);
#endif
#ifdef SIGTERM
if(SIG_ERR != old_sigterm_handler)
(void)set_signal(SIGTERM, old_sigterm_handler, FALSE);
#endif
#if defined(SIGBREAK) && defined(_WIN32)
if(SIG_ERR != old_sigbreak_handler)
(void)set_signal(SIGBREAK, old_sigbreak_handler, FALSE);
#endif
#ifdef _WIN32
#ifndef UNDER_CE
(void)SetConsoleCtrlHandler(ctrl_event_handler, FALSE);
#endif
#if !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)
if(thread_main_window && thread_main_id) {
if(PostThreadMessage(thread_main_id, WM_APP, 0, 0)) {
if(WaitForSingleObjectEx(thread_main_window, INFINITE, TRUE)) {
if(CloseHandle(thread_main_window)) {
thread_main_window = NULL;
thread_main_id = 0;
}
}
}
}
if(exit_event) {
if(CloseHandle(exit_event)) {
exit_event = NULL;
}
}
#endif
#endif
}
#ifdef USE_UNIX_SOCKETS
int bind_unix_socket(curl_socket_t sock, const char *unix_socket,
struct sockaddr_un *sau)
{
int error;
char errbuf[STRERROR_LEN];
int rc;
size_t len = strlen(unix_socket);
memset(sau, 0, sizeof(struct sockaddr_un));
sau->sun_family = AF_UNIX;
if(len >= sizeof(sau->sun_path) - 1) {
logmsg("Too long unix socket domain path (%zd)", len);
return -1;
}
strcpy(sau->sun_path, unix_socket);
rc = bind(sock, (struct sockaddr*)sau, sizeof(struct sockaddr_un));
if(rc && SOCKERRNO == SOCKEADDRINUSE) {
struct_stat statbuf;
/* socket already exists. Perhaps it is stale? */
curl_socket_t unixfd = socket(AF_UNIX, SOCK_STREAM, 0);
if(CURL_SOCKET_BAD == unixfd) {
logmsg("Failed to create socket at %s (%d) %s", unix_socket,
SOCKERRNO, curlx_strerror(SOCKERRNO, errbuf, sizeof(errbuf)));
return -1;
}
/* check whether the server is alive */
rc = connect(unixfd, (struct sockaddr*)sau, sizeof(struct sockaddr_un));
error = SOCKERRNO;
sclose(unixfd);
if(rc && error != SOCKECONNREFUSED) {
logmsg("Failed to connect to %s (%d) %s", unix_socket,
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
return rc;
}
/* socket server is not alive, now check if it was actually a socket. */
#ifdef _WIN32
/* Windows does not have lstat function. */
rc = curlx_win32_stat(unix_socket, &statbuf);
#else
rc = lstat(unix_socket, &statbuf);
#endif
if(rc) {
logmsg("Error binding socket, failed to stat %s (%d) %s", unix_socket,
errno, curlx_strerror(errno, errbuf, sizeof(errbuf)));
return rc;
}
#ifdef S_IFSOCK
if((statbuf.st_mode & S_IFSOCK) != S_IFSOCK) {
logmsg("Error binding socket, failed to stat %s", unix_socket);
return -1;
}
#endif
/* dead socket, cleanup and retry bind */
rc = unlink(unix_socket);
if(rc) {
logmsg("Error binding socket, failed to unlink %s (%d) %s", unix_socket,
errno, curlx_strerror(errno, errbuf, sizeof(errbuf)));
return rc;
}
/* stale socket is gone, retry bind */
rc = bind(sock, (struct sockaddr*)sau, sizeof(struct sockaddr_un));
}
return rc;
}
#endif
curl_socket_t sockdaemon(curl_socket_t sock,
unsigned short *listenport,
const char *unix_socket,
bool bind_only)
{
/* passive daemon style */
srvr_sockaddr_union_t listener;
int flag;
int rc;
int totdelay = 0;
int maxretr = 10;
int delay = 20;
int attempt = 0;
int error = 0;
char errbuf[STRERROR_LEN];
#ifndef USE_UNIX_SOCKETS
(void)unix_socket;
#endif
do {
attempt++;
flag = 1;
rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
(void *)&flag, sizeof(flag));
if(rc) {
error = SOCKERRNO;
logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s",
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
if(maxretr) {
rc = curlx_wait_ms(delay);
if(rc) {
/* should not happen */
error = SOCKERRNO;
logmsg("curlx_wait_ms() failed with error (%d) %s",
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
sclose(sock);
return CURL_SOCKET_BAD;
}
if(got_exit_signal) {
logmsg("signalled to die, exiting...");
sclose(sock);
return CURL_SOCKET_BAD;
}
totdelay += delay;
delay *= 2; /* double the sleep for next attempt */
}
}
} while(rc && maxretr--);
if(rc) {
logmsg("setsockopt(SO_REUSEADDR) failed %d times in %d ms. Error (%d) %s",
attempt, totdelay,
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
logmsg("Continuing anyway...");
}
/* When the specified listener port is zero, it is actually a
request to let the system choose a non-zero available port. */
switch(socket_domain) {
case AF_INET:
memset(&listener.sa4, 0, sizeof(listener.sa4));
listener.sa4.sin_family = AF_INET;
listener.sa4.sin_addr.s_addr = INADDR_ANY;
listener.sa4.sin_port = htons(*listenport);
rc = bind(sock, &listener.sa, sizeof(listener.sa4));
break;
#ifdef USE_IPV6
case AF_INET6:
memset(&listener.sa6, 0, sizeof(listener.sa6));
listener.sa6.sin6_family = AF_INET6;
listener.sa6.sin6_addr = in6addr_any;
listener.sa6.sin6_port = htons(*listenport);
rc = bind(sock, &listener.sa, sizeof(listener.sa6));
break;
#endif /* USE_IPV6 */
#ifdef USE_UNIX_SOCKETS
case AF_UNIX:
rc = bind_unix_socket(sock, unix_socket, &listener.sau);
break;
#endif
default:
rc = 1;
}
if(rc) {
error = SOCKERRNO;
#ifdef USE_UNIX_SOCKETS
if(socket_domain == AF_UNIX)
logmsg("Error binding socket on path %s (%d) %s", unix_socket,
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
else
#endif
logmsg("Error binding socket on port %hu (%d) %s", *listenport,
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
sclose(sock);
return CURL_SOCKET_BAD;
}
if(!*listenport
#ifdef USE_UNIX_SOCKETS
&& !unix_socket
#endif
) {
/* The system was supposed to choose a port number, figure out which
port we actually got and update the listener port value with it. */
curl_socklen_t la_size;
srvr_sockaddr_union_t localaddr;
#ifdef USE_IPV6
if(socket_domain == AF_INET6)
la_size = sizeof(localaddr.sa6);
else
#endif
la_size = sizeof(localaddr.sa4);
memset(&localaddr.sa, 0, (size_t)la_size);
if(getsockname(sock, &localaddr.sa, &la_size) < 0) {
error = SOCKERRNO;
logmsg("getsockname() failed with error (%d) %s",
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
sclose(sock);
return CURL_SOCKET_BAD;
}
switch(localaddr.sa.sa_family) {
case AF_INET:
*listenport = ntohs(localaddr.sa4.sin_port);
break;
#ifdef USE_IPV6
case AF_INET6:
*listenport = ntohs(localaddr.sa6.sin6_port);
break;
#endif
default:
break;
}
if(!*listenport) {
/* Real failure, listener port shall not be zero beyond this point. */
logmsg("Apparently getsockname() succeeded, with listener port zero.");
logmsg("A valid reason for this failure is a binary built without");
logmsg("proper network library linkage. This might not be the only");
logmsg("reason, but double check it before anything else.");
sclose(sock);
return CURL_SOCKET_BAD;
}
}
/* bindonly option forces no listening */
if(bind_only) {
logmsg("instructed to bind port without listening");
return sock;
}
/* start accepting connections */
rc = listen(sock, 5);
if(rc) {
error = SOCKERRNO;
logmsg("listen(%ld, 5) failed with error (%d) %s", (long)sock,
error, curlx_strerror(error, errbuf, sizeof(errbuf)));
sclose(sock);
return CURL_SOCKET_BAD;
}
return sock;
}