diff --git a/booth.spec.in b/booth.spec.in index 9f67fff..4961763 100644 --- a/booth.spec.in +++ b/booth.spec.in @@ -1,279 +1,279 @@ # keep around ready for later user @ALPHATAG@ @NUMCOMM@ @DIRTY@ %global gitver %{?numcomm:.%{numcomm}}%{?alphatag:-%{alphatag}}%{?dirty:-%{dirty}} %global gittarver %{?numcomm:-%{numcomm}}%{?alphatag:-g%{alphatag}}%{?dirty:-%{dirty}} %@bcond_html_man@ html_man %@bcond_glue@ glue %@bcond_python3@ python3 %@bcond_run_build_tests@ run_build_tests ## User and group to use for nonprivileged services (should be in sync with pacemaker) %global uname @uname@ %global gname @gname@ # Defined on RHEL 8 and Fedora %{!?__python2: %global __python2 /usr/bin/python2} %{!?__python3: %global __python3 /usr/bin/python3} %if 0%{?with_python3} %global __python_full_path %{__python3} %else %global __python_full_path %{__python2} %endif %if 0%{?suse_version} %global booth_docdir %{_defaultdocdir}/%{name} %else # newer fedora distros have _pkgdocdir, rely on that when # available %{!?_pkgdocdir: %global _pkgdocdir %%{_docdir}/%{name}-%{version}} # Directory where we install documentation %global booth_docdir %{_pkgdocdir} %endif %global test_path %{_datadir}/booth/tests %if 0%{?suse_version} %define _libexecdir %{_libdir} %define _fwdefdir %{_libexecdir}/firewalld/services %endif %define with_extra_warnings 0 %define with_debugging 0 %define without_fatal_warnings 1 %if 0%{?fedora} || 0%{?centos} || 0%{?rhel} %define pkg_group System Environment/Daemons %else %define pkg_group Productivity/Clustering/HA %endif %global release 1 Name: booth Url: https://github.com/ClusterLabs/booth Summary: Ticket Manager for Multi-site Clusters License: GPL-2.0-or-later Group: %{pkg_group} Version: @version@ Release: %{?numcomm:%{numcomm}.}%{release}%{?alphatag:.%{alphatag}}%{?dirty:.%{dirty}}%{?alphatag:.git}%{?dist} Source: https://github.com/ClusterLabs/booth/archive/v%{version}%{?gittarver}/%{name}-%{version}%{?gitver}.tar.gz Source1: %name-rpmlintrc BuildRequires: @asciidoc@ BuildRequires: autoconf BuildRequires: automake BuildRequires: pkgconfig %if 0%{?suse_version} BuildRequires: glib2-devel # SuSEFirewall2 replaced by Firewalld (fate#323460) BuildRequires: firewall-macros %else BuildRequires: pkgconfig(glib-2.0) %endif -BuildRequires: libgcrypt-devel +BuildRequires: gnutls-devel %if 0%{?fedora} || 0%{?centos} || 0%{?rhel} BuildRequires: pacemaker-libs-devel %else %if 0%{?suse_version} > 1500 BuildRequires: libpacemaker3-devel %else BuildRequires: libpacemaker-devel %endif %endif %if 0%{?with_glue} %if 0%{?fedora} || 0%{?centos} || 0%{?rhel} BuildRequires: cluster-glue-libs-devel %else BuildRequires: libglue-devel %endif %else # logging provider BuildRequires: pkgconfig(libqb) # random2range provider BuildRequires: pkgconfig(glib-2.0) # nametag provider BuildRequires: pkgconfig(libsystemd) %endif BuildRequires: libxml2-devel BuildRequires: zlib-devel %if 0%{?fedora} || 0%{?centos} || 0%{?rhel} Requires: pacemaker >= 1.1.8 %if 0%{?with_glue} Requires: cluster-glue-libs >= 1.0.6 %endif %else Requires: pacemaker-ticket-support >= 2.0 %endif # for check scriptlet %if 0%{?with_python3} BuildRequires: python3-devel %else BuildRequires: python %endif # For Fedora compatibility Provides: booth-core Provides: booth-site Provides: booth-arbitrator %description Booth manages tickets which authorize cluster sites located in geographically dispersed locations to run resources. It facilitates support of geographically distributed clustering in Pacemaker. %prep %setup -q -n %{name}-%{version}%{?gitver} %build ./autogen.sh %configure \ --with-initddir=%{_initrddir} \ --docdir=%{booth_docdir} \ %{?with_html_man:--with-html_man} \ %{!?with_glue:--without-glue} \ PYTHON=%{__python_full_path} make %install make DESTDIR=$RPM_BUILD_ROOT install docdir=%{booth_docdir} mkdir -p %{buildroot}/%{_mandir}/man8/ gzip < docs/boothd.8 > %{buildroot}/%{_mandir}/man8/booth.8.gz ln %{buildroot}/%{_mandir}/man8/booth.8.gz %{buildroot}/%{_mandir}/man8/boothd.8.gz %if %{defined _unitdir} # systemd mkdir -p %{buildroot}/%{_unitdir} cp -a conf/booth@.service %{buildroot}/%{_unitdir}/booth@.service cp -a conf/booth-arbitrator.service %{buildroot}/%{_unitdir}/booth-arbitrator.service ln -s /usr/sbin/service %{buildroot}%{_sbindir}/rcbooth-arbitrator %else # sysV init ln -s ../../%{_initddir}/booth-arbitrator %{buildroot}%{_sbindir}/rcbooth-arbitrator %endif #install test-parts mkdir -p %{buildroot}/%{test_path}/conf cp -a test %{buildroot}/%{test_path}/ cp -a conf/booth.conf.example %{buildroot}/%{test_path}/conf/ chmod +x %{buildroot}/%{test_path}/test/booth_path chmod +x %{buildroot}/%{test_path}/test/live_test.sh mkdir -p %{buildroot}/%{test_path}/src/ ln -s %{_sbindir}/boothd %{buildroot}/%{test_path}/src/ rm -f %{buildroot}/%{test_path}/test/*.pyc # Generate runtests.py and boothtestenv.py sed -e 's#PYTHON_SHEBANG#%{__python_full_path} -Es#g' \ -e 's#TEST_SRC_DIR#%{test_path}/test#g' \ -e 's#TEST_BUILD_DIR#%{test_path}/test#g' \ %{buildroot}/%{test_path}/test/runtests.py.in > %{buildroot}/%{test_path}/test/runtests.py chmod +x %{buildroot}/%{test_path}/test/runtests.py sed -e 's#PYTHON_SHEBANG#%{__python_full_path} -Es#g' \ -e 's#TEST_SRC_DIR#%{test_path}/test#g' \ -e 's#TEST_BUILD_DIR#%{test_path}/test#g' \ %{buildroot}/%{test_path}/test/boothtestenv.py.in > %{buildroot}/%{test_path}/test/boothtestenv.py %if 0%{?suse_version} #Firewalld rule mkdir -p $RPM_BUILD_ROOT/%{_fwdefdir} install -m 644 contrib/geo-cluster.firewalld.xml $RPM_BUILD_ROOT/%{_fwdefdir}/booth.xml #install -m 644 %{S:2} $RPM_BUILD_ROOT/%{_fwdefdir}/booth %post %firewalld_reload %endif %check %if 0%{?with_run_build_tests} echo "%%with_run_build_tests set to %with_run_build_tests; including tests" make check %else echo "%%with_run_build_tests set to %with_run_build_tests; skipping tests" %endif %files %{_sbindir}/booth %{_sbindir}/boothd %{_sbindir}/booth-keygen %{_sbindir}/geostore %{_mandir}/man8/booth.8.gz %{_mandir}/man8/boothd.8.gz %{_mandir}/man8/booth-keygen.8.gz %{_mandir}/man8/geostore.8.gz %dir /usr/lib/ocf %dir /usr/lib/ocf/resource.d %dir /usr/lib/ocf/resource.d/pacemaker %dir /usr/lib/ocf/resource.d/booth %dir /usr/lib/ocf/lib %dir /usr/lib/ocf/lib/booth %dir %{_sysconfdir}/booth %dir %attr (750, %{uname}, %{gname}) %{_var}/lib/booth/ %dir %attr (750, %{uname}, %{gname}) %{_var}/lib/booth/cores %{_sbindir}/rcbooth-arbitrator /usr/lib/ocf/resource.d/pacemaker/booth-site /usr/lib/ocf/lib/booth/geo_attr.sh /usr/lib/ocf/resource.d/booth/geostore %config %{_sysconfdir}/booth/booth.conf.example %if 0%{?suse_version} %dir %{_libexecdir}/firewalld %dir %{_fwdefdir} %{_fwdefdir}/booth.xml %endif %if %{defined _unitdir} %{_unitdir}/booth@.service %{_unitdir}/booth-arbitrator.service %exclude %{_initddir}/booth-arbitrator %else %{_initddir}/booth-arbitrator %endif %dir %{_datadir}/booth %{_datadir}/booth/service-runnable %dir %{_datadir}/pkgconfig %{_datadir}/pkgconfig/booth.pc %doc AUTHORS README COPYING %doc README.upgrade-from-v0.1 %if 0%{?with_html_man} %{booth_docdir}/booth-keygen.8.html %{booth_docdir}/boothd.8.html %{booth_docdir}/geostore.8.html %endif %package test Summary: Test scripts for Booth Group: %{pkg_group} Requires: booth Requires: gdb Requires: %{__python_full_path} %if 0%{?with_python3} Requires: python3-pexpect %else Requires: python-pexpect %endif %description test This package contains automated tests for Booth, the Cluster Ticket Manager for Pacemaker. %files test %doc %{booth_docdir}/README-testing %{test_path} %dir /usr/lib/ocf %dir /usr/lib/ocf/resource.d %dir /usr/lib/ocf/resource.d/booth /usr/lib/ocf/resource.d/booth/sharedrsc %changelog diff --git a/configure.ac b/configure.ac index eb1d1e1..4c4a0af 100644 --- a/configure.ac +++ b/configure.ac @@ -1,512 +1,535 @@ # -*- Autoconf -*- # Process this file with autoconf to produce a configure script. # bootstrap / init AC_PREREQ([2.69]) AC_INIT([booth], [m4_esyscmd([build-aux/git-version-gen --fallback 1.1 .tarball-version .gitarchivever])], [users@clusterlabs.org]) AC_USE_SYSTEM_EXTENSIONS AM_INIT_AUTOMAKE([-Wno-portability subdir-objects]) AC_CONFIG_MACRO_DIR([build-aux]) AC_CONFIG_SRCDIR([src/main.c]) AC_CONFIG_HEADERS([src/b_config.h src/booth_config.h]) AC_CANONICAL_HOST AC_LANG([C]) AC_SUBST(WITH_LIST, [""]) dnl Fix default variables - "prefix" variable if not specified if test "$prefix" = "NONE"; then prefix="/usr" dnl Fix "localstatedir" variable if not specified if test "$localstatedir" = "\${prefix}/var"; then localstatedir="/var" fi dnl Fix "sysconfdir" variable if not specified if test "$sysconfdir" = "\${prefix}/etc"; then sysconfdir="/etc" fi dnl Fix "libdir" variable if not specified if test "$libdir" = "\${exec_prefix}/lib"; then if test -e /usr/lib64; then libdir="/usr/lib64" else libdir="/usr/lib" fi fi fi AC_MSG_NOTICE(Sanitizing exec_prefix: ${exec_prefix}) case $exec_prefix in dnl For consistency with Corosync, map NONE->$prefix NONE) exec_prefix=$prefix;; prefix) exec_prefix=$prefix;; esac # Checks for programs. # check stolen from gnulib/m4/gnu-make.m4 if ! ${MAKE-make} --version /cannot/make/this >/dev/null 2>&1; then AC_MSG_ERROR([you don't seem to have GNU make; it is required]) fi AC_PROG_CC AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET AC_PROG_RANLIB PKG_PROG_PKG_CONFIG AC_PATH_PROGS(ASCIIDOC, asciidoc) AC_PATH_PROGS(ASCIIDOCTOR, asciidoctor) AC_PATH_PROGS(A2X, a2x) AM_CONDITIONAL(IS_ASCIIDOC, test x"${ASCIIDOC}" != x"") AM_CONDITIONAL(IS_A2X, test x"${A2X}" != x"") AM_CONDITIONAL(BUILD_ASCIIDOC, test x"${A2X}" != x"" || test x"${ASCIIDOCTOR}" != x"") -# libgcrypt or mhash for hmac -libgcrypt_installed="yes" -AC_CHECK_HEADERS(gcrypt.h, , [libgcrypt_installed="no"],) -AC_CHECK_LIB(gcrypt, gcry_md_open, , [libgcrypt_installed="no"]) -AM_CONDITIONAL(BUILD_AUTH_C, test "x${libgcrypt_installed}" = "xyes") - -if test "x$libgcrypt_installed" = "xno"; then - mhash_installed="yes" - AC_CHECK_HEADERS(mhash.h, , [mhash_installed="no"],) - AC_CHECK_LIB(mhash, mhash_init, , [mhash_installed="no"]) - AM_CONDITIONAL(BUILD_AUTH_C, test "x${mhash_installed}" = "xyes") -fi - AC_CHECK_LIB([xml2], xmlReadDoc) PKG_CHECK_MODULES(XML, [libxml-2.0]) PKG_CHECK_MODULES(GLIB, [glib-2.0]) PKG_CHECK_MODULES([PCMK], [pacemaker-service],, [PKG_CHECK_MODULES([PCMK], [pcmk-service])]) # Python casing, prefer 3.3+ to 2.{6...} if test "x$PYTHON" = "x"; then AM_PATH_PYTHON([3.3],, [AM_PATH_PYTHON([2.6])]) else # Just set Automake variables (mainly PYTHON_VERSION) AM_PATH_PYTHON fi PYTHON_SHEBANG="$PYTHON ${PYTHON_OPTS--Es}" AC_SUBST([PYTHON_SHEBANG]) AM_CONDITIONAL(PYTHON_IS_VERSION3, test "x${PYTHON_VERSION%%.*}" = "x3") # Checks for header files. AC_FUNC_ALLOCA AC_HEADER_DIRENT AC_HEADER_SYS_WAIT AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h netdb.h netinet/in.h stdint.h \ stdlib.h string.h sys/ioctl.h sys/param.h sys/socket.h \ sys/time.h syslog.h unistd.h sys/types.h getopt.h malloc.h \ sys/sockio.h utmpx.h]) AC_CHECK_HEADERS(heartbeat/glue_config.h) -AC_CHECK_HEADERS(mhash.h) AC_CHECK_HEADER([zlib.h], [AC_SUBST(ZLIB_LIBS, ["-lz"])], [AC_MSG_ERROR([zlib development files required])]) saved_CPPFLAGS="${CPPFLAGS}" CPPFLAGS="${CPPFLAGS} ${PCMK_CFLAGS} ${GLIB_CFLAGS}" AC_CHECK_HEADER([crm/services.h], [], [AC_MSG_ERROR([crm/services.h header required])]) CPPFLAGS="${saved_CPPFLAGS}" # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_TYPE_UID_T AC_C_INLINE AC_TYPE_INT16_T AC_TYPE_INT32_T AC_TYPE_INT64_T AC_TYPE_INT8_T AC_TYPE_SIZE_T AC_TYPE_SSIZE_T AC_TYPE_UINT16_T AC_TYPE_UINT32_T AC_TYPE_UINT64_T AC_TYPE_UINT8_T AC_C_VOLATILE # Checks for library functions. AC_FUNC_CLOSEDIR_VOID AC_FUNC_ERROR_AT_LINE AC_REPLACE_FNMATCH AC_FUNC_FORK AC_PROG_GCC_TRADITIONAL AC_FUNC_MALLOC AC_FUNC_MEMCMP AC_FUNC_REALLOC AC_FUNC_SELECT_ARGTYPES AC_FUNC_VPRINTF AC_CHECK_FUNCS([alarm alphasort atexit bzero dup2 endgrent endpwent fcntl \ getcwd getpeerucred getpeereid gettimeofday memmove \ memset mkdir scandir select socket strcasecmp strchr strdup \ strerror strrchr strspn strstr \ sched_get_priority_max sched_setscheduler]) AC_CONFIG_FILES([Makefile booth.pc src/Makefile docs/Makefile conf/Makefile]) AC_CONFIG_FILES([conf/booth-arbitrator.service conf/booth@.service]) AC_CONFIG_FILES([script/service-runnable], [chmod +x script/service-runnable]) # =============================================== # Helpers # =============================================== ## PKG_CHECK_VAR wrapper that allows defining a default value ## when value from pkg-config is not detected AC_DEFUN([BOOTH_PKG_CHECK_VAR], [ varname=$1 default=$4 AC_MSG_CHECKING([for pkg-conf $2 var $3]) PKG_CHECK_VAR([$1], [$2], [$3]) AS_VAR_IF([$1], [""], [AS_VAR_IF([default], [""], AC_MSG_ERROR([not found]), [AS_VAR_COPY([$varname], [default]) && AC_MSG_RESULT([not found, using default ${!varname}])])], [AC_MSG_RESULT([yes (detected: ${!varname})])]) ]) ## helper for CC stuff cc_supports_flag() { local CFLAGS="-Werror $@" AC_MSG_CHECKING(whether $CC supports "$@") AC_COMPILE_IFELSE([AC_LANG_SOURCE([[int main(){return 0;}]])], [RC=0; AC_MSG_RESULT(yes)],[RC=1; AC_MSG_RESULT(no)]) return $RC } ## local defines PACKAGE_FEATURES="" LINT_FLAGS="-weak -unrecog +posixlib +ignoresigns -fcnuse \ -badflag -D__gnuc_va_list=va_list -D__attribute\(x\)=" # local options AC_ARG_ENABLE([fatal-warnings], [ --enable-fatal-warnings : enable fatal warnings. ], [ default="no" ]) AC_ARG_ENABLE([debug], [ --enable-debug : enable debug build. ], [ default="no" ]) AC_ARG_ENABLE([user-flags], [ --enable-user-flags : rely on user environment. ], [ default="no" ]) AC_ARG_ENABLE([coverage], [ --enable-coverage : coverage analysis of the codebase. ], [ default="no" ]) AC_ARG_WITH([initddir], [ --with-initddir=DIR : path to init script directory. ], [ INITDDIR="$withval" ], [ INITDDIR="$sysconfdir/init.d" ]) AC_ARG_WITH([html_man], [ --with-html_man : Enable generating man pages in HTML.]) AM_CONDITIONAL(BUILD_ASCIIDOC_HTML_MAN, (test "x${ASCIIDOC}" != "x" || test x"${ASCIIDOCTOR}" != x"") && test "x$with_html_man" = "xyes") AC_ARG_WITH([glue], [ --without-glue : Avoid libraries from (cluster-)glue project.], [], [with_glue=yes]) AC_ARG_WITH([run_build_tests], [ --with-run-build-tests : Enable running build tests when generating RPM], [run_build_tests=yes], []) AM_CONDITIONAL([RUN_BUILD_TESTS], [test "x$run_build_tests" = "xyes"]) # figure out ocfdir automatically and allow manual override (mostly for CI) BOOTH_PKG_CHECK_VAR([OCFROOT], [resource-agents], [ocfrootdir], [/usr/lib/ocf]) AC_ARG_WITH([ocfdir], [ --with-ocfdir=DIR : path to ocfdir (default: autodetected). ], [ ocfdir="$withval" ], [ ocfdir="$OCFROOT" ]) AC_SUBST([ocfdir]) +AC_ARG_WITH([hmac_library], + [ --with-hmac-library=LIBRARY : Select HMAC library to use (default: autodetect one of gnutls, gcrypt or mhash).]) + +# GnuTLS, libgcrypt or mhash for hmac +hmac_library_installed="no" +if test "x$with_hmac_library" == "x" && test "x$hmac_library_installed" == "xno" || \ + test "x$with_hmac_library" == "xgnutls"; then + libgnutls_installed="yes" + PKG_CHECK_MODULES([LIBGNUTLS], [gnutls >= 2.10.0], [ + AC_DEFINE([HAVE_LIBGNUTLS], [1], [Have gnutls library]) + libgnutls_installed="yes" + ], [libgnutls_installed="no"]) + hmac_library_installed="${libgnutls_installed}" +fi + +if test "x$with_hmac_library" == "x" && test "x$hmac_library_installed" == "xno" || \ + test "x$with_hmac_library" == "xgcrypt"; then + libgcrypt_installed="yes" + AC_CHECK_HEADERS(gcrypt.h, , [libgcrypt_installed="no"],) + AC_CHECK_LIB(gcrypt, gcry_md_open, , [libgcrypt_installed="no"]) + hmac_library_installed="${libgcrypt_installed}" +fi + +if test "x$with_hmac_library" == "x" && test "x$hmac_library_installed" == "xno" || \ + test "x$with_hmac_library" == "xmhash"; then + mhash_installed="yes" + AC_CHECK_HEADERS(mhash.h, , [mhash_installed="no"],) + AC_CHECK_LIB(mhash, mhash_init, , [mhash_installed="no"]) + hmac_library_installed="${mhash_installed}" +fi + +if test "x$with_hmac_library" != "x" && test "x$hmac_library_installed" == "xno";then + AC_MSG_ERROR([required HMAC library not detected]) +fi + +AM_CONDITIONAL(BUILD_AUTH_C, test "x${hmac_library_installed}" = "xyes") + # figure out logging provider logging_provider="" if test "x$logging_provider" = "x" && test "x$with_glue" = "xyes"; then AC_CHECK_LIB([plumb], [cl_log], [logging_provider="libplumb"]) fi if test "x$logging_provider" = "x" && test "x$with_glue" = "xno"; then PKG_CHECK_MODULES([LIBQB], [libqb]) AC_DEFINE([LOGGING_LIBQB], [], [use libqb as a logging provider]) PKG_CHECK_MODULES([LIBQB1], [libqb >= 1.0], [AC_DEFINE([LOGGING_LIBQB_MAJOR], [1], [libqb major version lower bound])], [AC_MSG_WARN([[syslog identifier will not get changed]])]) logging_provider=libqb fi case "$logging_provider" in libplumb) LOGGER="ha_logger" ;; libqb) LOGGER="logger -t booth-script" ;; *) AC_MSG_ERROR([logging provider required (libplumb, or libqb when --without-glue)]) ;; esac AM_CONDITIONAL([LOGGING_LIBQB], [test "x$logging_provider" = "xlibqb"]) AC_SUBST([LOGGER]) # figure out range2random provider range2random_provider="" if test "x$range2random_provider" = "x" && test "x$with_glue" = "xyes"; then AC_CHECK_LIB([plumb], [get_next_random], [range2random_provider="libplumb"]) AC_CHECK_DECL([cl_rand_from_interval], [], [range2random_provider=""], [#include ]) fi if test "x$range2random_provider" = "x" && test "x$with_glue" = "xno"; then AC_CHECK_LIB([glib-2.0], [g_random_int_range], [range2random_provider="glib"]) fi case "$range2random_provider" in libplumb) ;; glib) PKG_CHECK_MODULES([GLIB], [glib-2.0]) AC_DEFINE([RANGE2RANDOM_GLIB], [], [use glib as a range2random provider]) ;; *) AC_MSG_ERROR([range2random provider required (libplumb, or glib when --without-glue)]) ;; esac AM_CONDITIONAL([RANGE2RANDOM_GLIB], [test "x$range2random_provider" = "xglib"]) # figure out nametag/distinguished-role provider nametag_provider="" if test "x$nametag_provider" = "x" && test "x$with_glue" != "xno"; then AC_CHECK_LIB([plumbgpl], [set_proc_title], [nametag_provider="libplumbgpl"]) fi if test "x$nametag_provider" = "x" && test "x$with_glue" = "xno"; then AC_SEARCH_LIBS([sd_notify], [systemd systemd-daemon], [nametag_provider="libsystemd"]) fi NOTIFY_ACCESS_SWITCH='# ' case "$nametag_provider" in libplumbgpl) ;; libsystemd) PKG_CHECK_MODULES([LIBSYSTEMD], [libsystemd],, [ PKG_CHECK_MODULES([LIBSYSTEMD], [libsystemd-daemon]) ]) AC_DEFINE([NAMETAG_LIBSYSTEMD], [], [use libsystemd as a nametag provider]) NOTIFY_ACCESS_SWITCH= ;; *) AC_MSG_ERROR([nametag provider required (libplumbgpl, or libsystemd when --without-glue)]) ;; esac AM_CONDITIONAL([NAMETAG_LIBSYSTEMD], [test "x$nametag_provider" = "xlibsystemd"]) AC_SUBST([NOTIFY_ACCESS_SWITCH]) # figure out if "coredump nursing" supported and desired coredump_nursing="no" if test "x$with_glue" != "xno"; then AC_CHECK_LIB([plumb], [cl_enable_coredumps], [coredump_nursing="libplumb"]) fi if test "x$coredump_nursing" != "xno"; then AC_DEFINE(COREDUMP_NURSING, [], [eligible for coredump nursing]) fi AM_CONDITIONAL([COREDUMP_NURSING], [test "x$coredump_nursing" != "xno"]) # define CRM daemon user & group BOOTH_PKG_CHECK_VAR([CRM_DAEMON_USER], [pacemaker], [daemon_user], [hacluster]) AC_DEFINE_UNQUOTED(CRM_DAEMON_USER,"$CRM_DAEMON_USER", User to run Booth daemon as) BOOTH_PKG_CHECK_VAR([CRM_DAEMON_GROUP], [pacemaker], [daemon_group], [haclient]) AC_DEFINE_UNQUOTED(CRM_DAEMON_GROUP,"$CRM_DAEMON_GROUP", Group to run Booth daemon as) # *FLAGS handling goes here ENV_CFLAGS="$CFLAGS" ENV_CPPFLAGS="$CPPFLAGS" ENV_LDFLAGS="$LDFLAGS" # debug build stuff if test "x${enable_debug}" = xyes; then AC_DEFINE_UNQUOTED([DEBUG], [1], [Compiling Debugging code]) OPT_CFLAGS="-O0 -U_FORTIFY_SOURCE" PACKAGE_FEATURES="$PACKAGE_FEATURES debug" else OPT_CFLAGS="-O3" fi # gdb flags if test "x${GCC}" = xyes; then GDB_FLAGS="-ggdb3" else GDB_FLAGS="-g" fi dnl Check for POSIX clock_gettime dnl AC_CACHE_CHECK([have clock_gettime],ac_cv_HAVE_CLOCK_GETTIME,[ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ struct timespec tv; clock_gettime(CLOCK_REALTIME, &tv); return 0;]])], [ac_cv_HAVE_CLOCK_GETTIME=yes], [ac_cv_HAVE_CLOCK_GETTIME=no])]) AM_CONDITIONAL(BUILD_TIMER_C, test x"$ac_cv_HAVE_CLOCK_GETTIME" = x"yes") # extra warnings EXTRA_WARNINGS="" WARNLIST=" all shadow missing-prototypes missing-declarations strict-prototypes declaration-after-statement pointer-arith write-strings bad-function-cast missing-format-attribute format=2 format-security format-nonliteral no-long-long unsigned-char gnu89-inline no-strict-aliasing " for j in $WARNLIST; do if cc_supports_flag -W$j; then EXTRA_WARNINGS="$EXTRA_WARNINGS -W$j"; fi done if test "x${enable_coverage}" = xyes && \ cc_supports_flag -ftest-coverage && \ cc_supports_flag -fprofile-arcs ; then AC_MSG_NOTICE([Enabling Coverage (enable -O0 by default)]) OPT_CFLAGS="-O0" COVERAGE_CFLAGS="-ftest-coverage -fprofile-arcs" COVERAGE_LDFLAGS="-ftest-coverage -fprofile-arcs" PACKAGE_FEATURES="$PACKAGE_FEATURES coverage" else COVERAGE_CFLAGS="" COVERAGE_LDFLAGS="" fi if test "x${enable_ansi}" = xyes && \ cc_supports_flag -std=iso9899:199409 ; then AC_MSG_NOTICE([Enabling ANSI Compatibility]) ANSI_CPPFLAGS="-ansi -D_GNU_SOURCE -DANSI_ONLY" PACKAGE_FEATURES="$PACKAGE_FEATURES ansi" else ANSI_CPPFLAGS="" fi if test "x${enable_fatal_warnings}" = xyes && \ cc_supports_flag -Werror ; then AC_MSG_NOTICE([Enabling Fatal Warnings (-Werror)]) WERROR_CFLAGS="-Werror" PACKAGE_FEATURES="$PACKAGE_FEATURES fatal-warnings" else WERROR_CFLAGS="" fi # don't add addtional cflags if test "x${enable_user_flags}" = xyes; then OPT_CFLAGS="" GDB_FLAGS="" EXTRA_WARNINGS="" fi # final build of *FLAGS CFLAGS="$ENV_CFLAGS $OPT_CFLAGS $GDB_FLAGS $OS_CFLAGS \ - $COVERAGE_CFLAGS $EXTRA_WARNINGS $WERROR_CFLAGS $NSS_CFLAGS" + $COVERAGE_CFLAGS $EXTRA_WARNINGS $WERROR_CFLAGS $LIBGNUTLS_CFLAGS" CPPFLAGS="$ENV_CPPFLAGS $ANSI_CPPFLAGS $OS_CPPFLAGS $GLIB_CFLAGS $RESMON_CFLAGS $XML_CFLAGS" LDFLAGS="$ENV_LDFLAGS $COVERAGE_LDFLAGS $OS_LDFLAGS" -LIBS="$LIBS $XML_LIBS" +LIBS="$LIBS $XML_LIBS $LIBGNUTLS_LIBS" # substitute what we need: AC_SUBST([INITDDIR]) AC_SUBST([LINT_FLAGS]) BOOTH_LIB_DIR=${localstatedir}/lib/booth BOOTH_CORE_DIR=${localstatedir}/lib/booth/cores BOOTHSYSCONFDIR=${sysconfdir}/booth AC_SUBST([HAVE_LOG_CIB_DIFF]) AC_SUBST([HAVE_XML_LOG_PATCHSET]) AC_SUBST([BOOTH_LIB_DIR]) AC_SUBST([BOOTH_CORE_DIR]) AC_SUBST([BOOTHSYSCONFDIR]) AC_DEFINE_UNQUOTED([BOOTH_LIB_DIR], "$(eval echo ${BOOTH_LIB_DIR})", [booth lib directory]) AC_DEFINE_UNQUOTED([BOOTH_CORE_DIR], "$(eval echo ${BOOTH_CORE_DIR})", [booth working directory]) AC_DEFINE_UNQUOTED([BOOTHSYSCONFDIR], "$(eval echo ${BOOTHSYSCONFDIR})", [booth config directory]) AC_DEFINE_UNQUOTED([PACKAGE_FEATURES], "${PACKAGE_FEATURES}", [booth built-in features]) AC_OUTPUT AC_MSG_RESULT([]) AC_MSG_RESULT([$PACKAGE configuration:]) AC_MSG_RESULT([ Version = ${VERSION}]) AC_MSG_RESULT([ Prefix = ${prefix}]) AC_MSG_RESULT([ Executables = ${sbindir}]) AC_MSG_RESULT([ Man pages = ${mandir}]) AC_MSG_RESULT([ Doc dir = ${docdir}]) AC_MSG_RESULT([ Libraries = ${libdir}]) AC_MSG_RESULT([ Header files = ${includedir}]) AC_MSG_RESULT([ Arch-independent files = ${datadir}]) AC_MSG_RESULT([ State information = ${localstatedir}]) AC_MSG_RESULT([ System configuration = ${sysconfdir}]) AC_MSG_RESULT([ System init.d directory = ${INITDDIR}]) AC_MSG_RESULT([ booth config dir = ${BOOTHSYSCONFDIR}]) AC_MSG_RESULT([ ocf dir = ${ocfdir}]) AC_MSG_RESULT([ Features = ${PACKAGE_FEATURES}]) AC_MSG_RESULT([ Logging provider = ${logging_provider}]) AC_MSG_RESULT([ Range2random provider = ${range2random_provider}]) AC_MSG_RESULT([ Nametag provider = ${nametag_provider}]) AC_MSG_RESULT([ Coredump nursing = ${coredump_nursing}]) AC_MSG_RESULT([ Working directory = ${BOOTH_CORE_DIR}]) AC_MSG_RESULT([ HA group name = ${CRM_DAEMON_GROUP}]) AC_MSG_RESULT([ HA user name = ${CRM_DAEMON_USER}]) AC_MSG_RESULT([]) AC_MSG_RESULT([$PACKAGE build info:]) AC_MSG_RESULT([ Default optimization = ${OPT_CFLAGS}]) AC_MSG_RESULT([ Default debug options = ${GDB_CFLAGS}]) AC_MSG_RESULT([ Extra compiler warnings = ${EXTRA_WARNING}]) AC_MSG_RESULT([ Env. defined CFLAG = ${ENV_CFLAGS}]) AC_MSG_RESULT([ Env. defined CPPFLAGS = ${ENV_CPPFLAGS}]) AC_MSG_RESULT([ Env. defined LDFLAGS = ${ENV_LDFLAGS}]) AC_MSG_RESULT([ ANSI defined CPPFLAGS = ${ANSI_CPPFLAGS}]) AC_MSG_RESULT([ Coverage CFLAGS = ${COVERAGE_CFLAGS}]) AC_MSG_RESULT([ Coverage LDFLAGS = ${COVERAGE_LDFLAGS}]) AC_MSG_RESULT([ Fatal War. CFLAGS = ${WERROR_CFLAGS}]) AC_MSG_RESULT([ Final CFLAGS = ${CFLAGS}]) AC_MSG_RESULT([ Final CPPFLAGS = ${CPPFLAGS}]) AC_MSG_RESULT([ Final LDFLAGS = ${LDFLAGS}]) diff --git a/src/auth.c b/src/auth.c index a3b3d20..26a220e 100644 --- a/src/auth.c +++ b/src/auth.c @@ -1,140 +1,218 @@ /* * Copyright (C) 2015 Dejan Muhamedagic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "auth.h" +#if HAVE_LIBGNUTLS +/* calculate the HMAC of the message in data and store it in result + * it is up to the caller to make sure that there's enough space + * at result for the MAC + */ +int calc_hmac(const void *data, size_t datalen, + int hid, unsigned char *result, char *key, unsigned int keylen) +{ + int rc; + + /* + * Only SHA1 is supported so we can hardcode GNUTLS_MAC_SHA1 + */ + if (hid != BOOTH_COMPAT_MHASH_SHA1) { + log_error("calc_hmac unsupported HMAC algorithm %u", hid); + return -1; + } + + /* + * This shouldn't happen but gnutls_hmac_fast segfault if key or + * data are NULL so it is better to check beforehand. + */ + if (data == NULL || key == NULL) { + log_error("calc_hmac data or key is NULL"); + return -1; + } + + rc = gnutls_hmac_fast(GNUTLS_MAC_SHA1, key, keylen, data, datalen, result); + if (rc) { + log_error("gnutls_hmac_fast: %s", gnutls_strerror(rc)); + return -1; + } + + return rc; +} + +/* test HMAC + */ +int verify_hmac(const void *data, size_t datalen, + int hid, unsigned char *hmac, char *key, int keylen) +{ + unsigned char *our_hmac; + int rc; + unsigned int hlen; + + /* + * Only SHA1 is supported so we can hardcode GNUTLS_MAC_SHA1 + */ + if (hid != BOOTH_COMPAT_MHASH_SHA1) { + log_error("verify_hmac unsupported HMAC algorithm %u", hid); + return -1; + } + + if (data == NULL || key == NULL) { + log_error("verify_hmac data or key is NULL"); + return -1; + } + + hlen = gnutls_hmac_get_len(GNUTLS_MAC_SHA1); + if (!hlen) + return -1; + + our_hmac = calloc(hlen, 1); + if (!our_hmac) + return -1; + + rc = calc_hmac(data, datalen, hid, our_hmac, key, keylen); + if (rc) + goto out_free; + rc = memcmp(our_hmac, hmac, hlen); + +out_free: + if (our_hmac) + free(our_hmac); + return rc; +} +#endif + #if HAVE_LIBGCRYPT /* calculate the HMAC of the message in data and store it in result * it is up to the caller to make sure that there's enough space * at result for the MAC */ int calc_hmac(const void *data, size_t datalen, int hid, unsigned char *result, char *key, unsigned int keylen) { static gcry_md_hd_t digest; gcry_error_t err; int hlen; hlen = gcry_md_get_algo_dlen(hid); if (!hlen) return -1; if (!digest) { err = gcry_md_open(&digest, hid, GCRY_MD_FLAG_HMAC); if (err) { log_error("gcry_md_open: %s", gcry_strerror(err)); return -1; } err = gcry_md_setkey(digest, key, keylen); if (err) { log_error("gcry_md_open: %s", gcry_strerror(err)); return -1; } } gcry_md_write(digest, data, datalen); memcpy(result, gcry_md_read(digest, 0), hlen); gcry_md_reset(digest); return 0; } /* test HMAC */ int verify_hmac(const void *data, size_t datalen, int hid, unsigned char *hmac, char *key, int keylen) { unsigned char *our_hmac; int rc; int hlen; hlen = gcry_md_get_algo_dlen(hid); if (!hlen) return -1; our_hmac = malloc(hlen); if (!our_hmac) return -1; rc = calc_hmac(data, datalen, hid, our_hmac, key, keylen); if (rc) goto out_free; rc = memcmp(our_hmac, hmac, hlen); out_free: if (our_hmac) free(our_hmac); return rc; } #endif #if HAVE_LIBMHASH /* calculate the HMAC of the message in data and store it in result * it is up to the caller to make sure that there's enough space * at result for the MAC */ int calc_hmac(const void *data, size_t datalen, hashid hid, unsigned char *result, char *key, int keylen) { MHASH td; size_t block_size; block_size = mhash_get_hash_pblock(hid); if (!block_size) return -1; td = mhash_hmac_init(hid, key, keylen, block_size); if (!td) return -1; (void)mhash(td, data, datalen); if (mhash_hmac_deinit(td, result)) return -1; return 0; } /* test HMAC */ int verify_hmac(const void *data, size_t datalen, hashid hid, unsigned char *hmac, char *key, int keylen) { MHASH td; unsigned char *our_hmac = NULL; int rc = -1; td = mhash_hmac_init(hid, key, keylen, mhash_get_hash_pblock(hid)); if (!td) return -1; our_hmac = malloc(mhash_get_block_size(hid)); if (!our_hmac) return -1; (void)mhash(td, data, datalen); if (mhash_hmac_deinit(td, our_hmac)) goto out_free; rc = memcmp(our_hmac, hmac, mhash_get_block_size(hid)); out_free: if (our_hmac) free(our_hmac); return rc; } #endif diff --git a/src/auth.h b/src/auth.h index 98f0286..be1fc9e 100644 --- a/src/auth.h +++ b/src/auth.h @@ -1,45 +1,64 @@ /* * Copyright (C) 2015 Dejan Muhamedagic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "b_config.h" #include "log.h" #include +#if HAVE_LIBGNUTLS + +#include +#include + +/* + * We need to stay backwards compatible. Both gcrypt and mhash defines + * SHA1 algorithm as 2. but GNUTLS_MAC_SHA1 is defined as 3, so hardcode + * 2 here and use correct value in auth.c + */ +#define BOOTH_COMPAT_MHASH_SHA1 2 +#define BOOTH_HASH BOOTH_COMPAT_MHASH_SHA1 + +int calc_hmac(const void *data, size_t datalen, + int hid, unsigned char *result, char *key, unsigned int keylen); +int verify_hmac(const void *data, size_t datalen, + int hid, unsigned char *hmac, char *key, int keylen); +#endif + #if HAVE_LIBGCRYPT #include #define BOOTH_HASH GCRY_MD_SHA1 int calc_hmac(const void *data, size_t datalen, int hid, unsigned char *result, char *key, unsigned int keylen); int verify_hmac(const void *data, size_t datalen, int hid, unsigned char *hmac, char *key, int keylen); #endif #if HAVE_LIBMHASH #include #define BOOTH_HASH MHASH_SHA1 int calc_hmac(const void *data, size_t datalen, hashid hid, unsigned char *result, char *key, int keylen); int verify_hmac(const void *data, size_t datalen, hashid hid, unsigned char *hmac, char *key, int keylen); #endif diff --git a/src/config.c b/src/config.c index f0ca4aa..a533da3 100644 --- a/src/config.c +++ b/src/config.c @@ -1,1062 +1,1062 @@ /* * Copyright (C) 2011 Jiaju Zhang * Copyright (C) 2013-2014 Philipp Marek * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "b_config.h" #include #include #include #include #include #include #include #include #include #include #include #include "booth.h" #include "config.h" #include "raft.h" #include "ticket.h" #include "log.h" static int ticket_size = 0; static int ticket_realloc(void) { const int added = 5; int had, want; void *p; had = booth_conf->ticket_allocated; want = had + added; p = realloc(booth_conf->ticket, sizeof(struct ticket_config) * want); if (!p) { log_error("can't alloc more tickets"); return -ENOMEM; } booth_conf->ticket = p; memset(booth_conf->ticket + had, 0, sizeof(struct ticket_config) * added); booth_conf->ticket_allocated = want; return 0; } static void hostname_to_ip(char * hostname) { struct addrinfo hints; struct addrinfo *result, *rp; int res; int addr_found = 0; const char *ntop_res; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; res = getaddrinfo(hostname, NULL, &hints, &result); if (res != 0) { log_error("can't find IP for the host \"%s\"", hostname); return; } /* Return the first found AF_INET or AF_INET6 address */ for (rp = result; rp && !addr_found; rp = rp->ai_next) { if (rp->ai_family != AF_INET && rp->ai_family != AF_INET6) { continue ; } switch (rp->ai_family) { case AF_INET: ntop_res = inet_ntop(rp->ai_family, &((struct sockaddr_in *)(rp->ai_addr))->sin_addr, hostname, BOOTH_NAME_LEN - 1); break; case AF_INET6: ntop_res = inet_ntop(rp->ai_family, &((struct sockaddr_in6 *)(rp->ai_addr))->sin6_addr, hostname, BOOTH_NAME_LEN - 1); break; } if (ntop_res) { /* buffer overflow will not happen (IPv6 notation < 63 chars), but suppress the warnings */ hostname[BOOTH_NAME_LEN - 1] = '\0'; addr_found = 1; } } if (!addr_found) { log_error("no IP addresses found for the host \"%s\"", hostname); } freeaddrinfo(result); } static int add_site(char *addr_string, int type) { int rv; struct booth_site *site; uLong nid; uint32_t mask; int i; rv = 1; if (booth_conf->site_count == MAX_NODES) { log_error("too many nodes"); goto out; } if (strnlen(addr_string, sizeof(booth_conf->site[0].addr_string)) >= sizeof(booth_conf->site[0].addr_string)) { log_error("site address \"%s\" too long", addr_string); goto out; } site = booth_conf->site + booth_conf->site_count; site->family = AF_INET; site->type = type; /* buffer overflow will not hapen (we've already checked that addr_string will fit incl. terminating '\0' above), but suppress the warnings with copying everything but the boundary byte, which is valid as-is, since this last byte will be safely pre-zeroed from the struct booth_config initialization */ strncpy(site->addr_string, addr_string, sizeof(site->addr_string) - 1); if (!(inet_pton(AF_INET, site->addr_string, &site->sa4.sin_addr) > 0) && !(inet_pton(AF_INET6, site->addr_string, &site->sa6.sin6_addr) > 0)) { /* Not a valid address, so let us try to convert it into an IP address */ hostname_to_ip(site->addr_string); } site->index = booth_conf->site_count; site->bitmask = 1 << booth_conf->site_count; /* Catch site overflow */ assert(site->bitmask); booth_conf->all_bits |= site->bitmask; if (type == SITE) booth_conf->sites_bits |= site->bitmask; site->tcp_fd = -1; booth_conf->site_count++; rv = 0; memset(&site->sa6, 0, sizeof(site->sa6)); nid = crc32(0L, NULL, 0); /* Using the ASCII representation in site->addr_string (both sizeof() * and strlen()) gives quite a lot of collisions; a brute-force run * from 0.0.0.0 to 24.0.0.0 gives ~4% collisions, and this tends to * increase even more. * Whether there'll be a collision in real-life, with 3 or 5 nodes, is * another question ... but for now get the ID from the binary * representation - that had *no* collisions up to 32.0.0.0. * Note that POSIX mandates inet_pton to arange the address pointed * to by "dst" in network byte order, assuring little/big-endianess * mutual compatibility. */ if (inet_pton(AF_INET, site->addr_string, &site->sa4.sin_addr) > 0) { site->family = AF_INET; site->sa4.sin_family = site->family; site->sa4.sin_port = htons(booth_conf->port); site->saddrlen = sizeof(site->sa4); site->addrlen = sizeof(site->sa4.sin_addr); site->site_id = crc32(nid, (void*)&site->sa4.sin_addr, site->addrlen); } else if (inet_pton(AF_INET6, site->addr_string, &site->sa6.sin6_addr) > 0) { site->family = AF_INET6; site->sa6.sin6_family = site->family; site->sa6.sin6_flowinfo = 0; site->sa6.sin6_port = htons(booth_conf->port); site->saddrlen = sizeof(site->sa6); site->addrlen = sizeof(site->sa6.sin6_addr); site->site_id = crc32(nid, (void*)&site->sa6.sin6_addr, site->addrlen); } else { log_error("Address string \"%s\" is bad", site->addr_string); rv = EINVAL; } /* Make sure we will never collide with NO_ONE, * or be negative (to get "get_local_id() < 0" working). */ mask = 1 << (sizeof(site->site_id)*8 -1); assert(NO_ONE & mask); site->site_id &= ~mask; /* Test for collisions with other sites */ for(i=0; iindex; i++) if (booth_conf->site[i].site_id == site->site_id) { log_error("Got a site-ID collision. Please file a bug on https://github.com/ClusterLabs/booth/issues/new, attaching the configuration file."); exit(1); } out: return rv; } inline static char *skip_while_in(const char *cp, int (*fn)(int), const char *allowed) { /* strchr() returns a pointer to the terminator if *cp == 0. */ while (*cp && (fn(*cp) || strchr(allowed, *cp))) cp++; /* discard "const" qualifier */ return (char*)cp; } inline static char *skip_while(char *cp, int (*fn)(int)) { while (fn(*cp)) cp++; return cp; } inline static char *skip_until(char *cp, char expected) { while (*cp && *cp != expected) cp++; return cp; } static inline int is_end_of_line(char *cp) { char c = *cp; return c == '\n' || c == 0 || c == '#'; } static int add_ticket(const char *name, struct ticket_config **tkp, const struct ticket_config *def) { int rv; struct ticket_config *tk; if (booth_conf->ticket_count == booth_conf->ticket_allocated) { rv = ticket_realloc(); if (rv < 0) return rv; } tk = booth_conf->ticket + booth_conf->ticket_count; booth_conf->ticket_count++; if (!check_max_len_valid(name, sizeof(tk->name))) { log_error("ticket name \"%s\" too long.", name); return -EINVAL; } if (find_ticket_by_name(name, NULL)) { log_error("ticket name \"%s\" used again.", name); return -EINVAL; } if (* skip_while_in(name, isalnum, "-/")) { log_error("ticket name \"%s\" invalid; only alphanumeric names.", name); return -EINVAL; } strcpy(tk->name, name); tk->timeout = def->timeout; tk->term_duration = def->term_duration; tk->retries = def->retries; memcpy(tk->weight, def->weight, sizeof(tk->weight)); tk->mode = def->mode; if (tkp) *tkp = tk; return 0; } static int postproc_ticket(struct ticket_config *tk) { if (!tk) return 1; if (!tk->renewal_freq) { tk->renewal_freq = tk->term_duration/2; } if (tk->timeout*(tk->retries+1) >= tk->renewal_freq) { log_error("%s: total amount of time to " "retry sending packets cannot exceed " "renewal frequency " "(%d*(%d+1) >= %d)", tk->name, tk->timeout, tk->retries, tk->renewal_freq); return 0; } return 1; } /* returns number of weights, or -1 on bad input. */ static int parse_weights(const char *input, int weights[MAX_NODES]) { int i, v; char *cp; for(i=0; i= MAX_ARGS) { log_error("too many arguments for the acquire-handler"); free(tk_test.path); return -1; } tk_test.argv[i++] = p; } while (p); return 0; } struct toktab grant_type[] = { { "auto", GRANT_AUTO}, { "manual", GRANT_MANUAL}, { NULL, 0}, }; struct toktab attr_op[] = { {"eq", ATTR_OP_EQ}, {"ne", ATTR_OP_NE}, {NULL, 0}, }; static int lookup_tokval(char *key, struct toktab *tab) { struct toktab *tp; for (tp = tab; tp->str; tp++) { if (!strcmp(tp->str, key)) return tp->val; } return 0; } /* attribute prerequisite */ static int parse_attr_prereq(char *val, struct ticket_config *tk) { struct attr_prereq *ap = NULL; char *p; ap = (struct attr_prereq *)calloc(1, sizeof(struct attr_prereq)); if (!ap) { log_error("out of memory"); return -1; } p = strtok(val, " \t"); if (!p) { log_error("not enough arguments to attr-prereq"); goto err_out; } ap->grant_type = lookup_tokval(p, grant_type); if (!ap->grant_type) { log_error("%s is not a grant type", p); goto err_out; } p = strtok(NULL, " \t"); if (!p) { log_error("not enough arguments to attr-prereq"); goto err_out; } if (!(ap->attr_name = strdup(p))) { log_error("out of memory"); goto err_out; } p = strtok(NULL, " \t"); if (!p) { log_error("not enough arguments to attr-prereq"); goto err_out; } ap->op = lookup_tokval(p, attr_op); if (!ap->op) { log_error("%s is not an attribute operation", p); goto err_out; } p = strtok(NULL, " \t"); if (!p) { log_error("not enough arguments to attr-prereq"); goto err_out; } if (!(ap->attr_val = strdup(p))) { log_error("out of memory"); goto err_out; } tk->attr_prereqs = g_list_append(tk->attr_prereqs, ap); if (!tk->attr_prereqs) { log_error("out of memory"); goto err_out; } return 0; err_out: if (ap) { if (ap->attr_val) free(ap->attr_val); if (ap->attr_name) free(ap->attr_name); free(ap); } return -1; } extern int poll_timeout; int read_config(const char *path, int type) { char line[1024]; char error_str_buf[1024]; FILE *fp; char *s, *key, *val, *end_of_key; const char *error; char *cp, *cp2; int i; int lineno = 0; int got_transport = 0; int min_timeout = 0; struct ticket_config defaults = { { 0 } }; struct ticket_config *current_tk = NULL; fp = fopen(path, "r"); if (!fp) { log_error("failed to open %s: %s", path, strerror(errno)); return -1; } booth_conf = malloc(sizeof(struct booth_config) + TICKET_ALLOC * sizeof(struct ticket_config)); if (!booth_conf) { fclose(fp); log_error("failed to alloc memory for booth config"); return -ENOMEM; } memset(booth_conf, 0, sizeof(struct booth_config) + TICKET_ALLOC * sizeof(struct ticket_config)); ticket_size = TICKET_ALLOC; booth_conf->proto = UDP; booth_conf->port = BOOTH_DEFAULT_PORT; booth_conf->maxtimeskew = BOOTH_DEFAULT_MAX_TIME_SKEW; booth_conf->authkey[0] = '\0'; /* Provide safe defaults. -1 is reserved, though. */ booth_conf->uid = -2; booth_conf->gid = -2; strcpy(booth_conf->site_user, "hacluster"); strcpy(booth_conf->site_group, "haclient"); strcpy(booth_conf->arb_user, "nobody"); strcpy(booth_conf->arb_group, "nobody"); parse_weights("", defaults.weight); defaults.clu_test.path = NULL; defaults.clu_test.pid = 0; defaults.clu_test.status = 0; defaults.clu_test.progstate = EXTPROG_IDLE; defaults.term_duration = DEFAULT_TICKET_EXPIRY; defaults.timeout = DEFAULT_TICKET_TIMEOUT; defaults.retries = DEFAULT_RETRIES; defaults.acquire_after = 0; defaults.mode = TICKET_MODE_AUTO; error = ""; log_debug("reading config file %s", path); while (fgets(line, sizeof(line), fp)) { lineno++; s = skip_while(line, isspace); if (is_end_of_line(s) || *s == '#') continue; key = s; /* Key */ end_of_key = skip_while_in(key, isalnum, "-_"); if (end_of_key == key) { error = "No key"; goto err; } if (!*end_of_key) goto exp_equal; /* whitespace, and something else but nothing more? */ s = skip_while(end_of_key, isspace); if (*s != '=') { exp_equal: error = "Expected '=' after key"; goto err; } s++; /* It's my buffer, and I terminate if I want to. */ /* But not earlier than that, because we had to check for = */ *end_of_key = 0; /* Value tokenizing */ s = skip_while(s, isspace); switch (*s) { case '"': case '\'': val = s+1; s = skip_until(val, *s); /* Terminate value */ if (!*s) { error = "Unterminated quoted string"; goto err; } /* Remove and skip quote */ *s = 0; s++; if (*(s = skip_while(s, isspace)) && *s != '#') { error = "Surplus data after value"; goto err; } *s = 0; break; case 0: no_value: error = "No value"; goto err; break; default: val = s; /* Rest of line. */ i = strlen(s); /* i > 0 because of "case 0" above. */ while (i > 0 && isspace(s[i-1])) i--; s += i; *s = 0; } if (val == s) goto no_value; if (strlen(key) > BOOTH_NAME_LEN || strlen(val) > BOOTH_NAME_LEN) { error = "key/value too long"; goto err; } if (strcmp(key, "transport") == 0) { if (got_transport) { error = "config file has multiple transport lines"; goto err; } if (strcasecmp(val, "UDP") == 0) booth_conf->proto = UDP; else if (strcasecmp(val, "SCTP") == 0) booth_conf->proto = SCTP; else { (void)snprintf(error_str_buf, sizeof(error_str_buf), "invalid transport protocol \"%s\"", val); error = error_str_buf; goto err; } got_transport = 1; continue; } if (strcmp(key, "port") == 0) { booth_conf->port = atoi(val); continue; } if (strcmp(key, "name") == 0) { safe_copy(booth_conf->name, val, BOOTH_NAME_LEN, "name"); continue; } -#if HAVE_LIBGCRYPT || HAVE_LIBMHASH +#if HAVE_LIBGNUTLS || HAVE_LIBGCRYPT || HAVE_LIBMHASH if (strcmp(key, "authfile") == 0) { safe_copy(booth_conf->authfile, val, BOOTH_PATH_LEN, "authfile"); continue; } if (strcmp(key, "maxtimeskew") == 0) { booth_conf->maxtimeskew = atoi(val); continue; } #endif if (strcmp(key, "site") == 0) { if (add_site(val, SITE)) goto err; continue; } if (strcmp(key, "arbitrator") == 0) { if (add_site(val, ARBITRATOR)) goto err; continue; } if (strcmp(key, "site-user") == 0) { safe_copy(booth_conf->site_user, optarg, BOOTH_NAME_LEN, "site-user"); continue; } if (strcmp(key, "site-group") == 0) { safe_copy(booth_conf->site_group, optarg, BOOTH_NAME_LEN, "site-group"); continue; } if (strcmp(key, "arbitrator-user") == 0) { safe_copy(booth_conf->arb_user, optarg, BOOTH_NAME_LEN, "arbitrator-user"); continue; } if (strcmp(key, "arbitrator-group") == 0) { safe_copy(booth_conf->arb_group, optarg, BOOTH_NAME_LEN, "arbitrator-group"); continue; } if (strcmp(key, "debug") == 0) { if (type != CLIENT && type != GEOSTORE) debug_level = max(debug_level, atoi(val)); continue; } if (strcmp(key, "ticket") == 0) { if (current_tk && strcmp(current_tk->name, "__defaults__")) { if (!postproc_ticket(current_tk)) { goto err; } } if (!strcmp(val, "__defaults__")) { current_tk = &defaults; } else if (add_ticket(val, ¤t_tk, &defaults)) { goto err; } continue; } /* current_tk must be allocated at this point, otherwise * we don't know to which ticket the key refers */ if (!current_tk) { (void)snprintf(error_str_buf, sizeof(error_str_buf), "Unexpected keyword \"%s\"", key); error = error_str_buf; goto err; } if (strcmp(key, "expire") == 0) { current_tk->term_duration = read_time(val); if (current_tk->term_duration <= 0) { error = "Expected time >0 for expire"; goto err; } continue; } if (strcmp(key, "timeout") == 0) { current_tk->timeout = read_time(val); if (current_tk->timeout <= 0) { error = "Expected time >0 for timeout"; goto err; } if (!min_timeout) { min_timeout = current_tk->timeout; } else { min_timeout = min(min_timeout, current_tk->timeout); } continue; } if (strcmp(key, "retries") == 0) { current_tk->retries = strtol(val, &s, 0); if (*s || s == val || current_tk->retries<3 || current_tk->retries > 100) { error = "Expected plain integer value in the range [3, 100] for retries"; goto err; } continue; } if (strcmp(key, "renewal-freq") == 0) { current_tk->renewal_freq = read_time(val); if (current_tk->renewal_freq <= 0) { error = "Expected time >0 for renewal-freq"; goto err; } continue; } if (strcmp(key, "acquire-after") == 0) { current_tk->acquire_after = read_time(val); if (current_tk->acquire_after < 0) { error = "Expected time >=0 for acquire-after"; goto err; } continue; } if (strcmp(key, "before-acquire-handler") == 0) { if (parse_extprog(val, current_tk)) { goto err; } continue; } if (strcmp(key, "attr-prereq") == 0) { if (parse_attr_prereq(val, current_tk)) { goto err; } continue; } if (strcmp(key, "mode") == 0) { current_tk->mode = retrieve_ticket_mode(val); continue; } if (strcmp(key, "weights") == 0) { if (parse_weights(val, current_tk->weight) < 0) goto err; continue; } (void)snprintf(error_str_buf, sizeof(error_str_buf), "Unknown keyword \"%s\"", key); error = error_str_buf; goto err; } fclose(fp); if ((booth_conf->site_count % 2) == 0) { log_warn("Odd number of nodes is strongly recommended!"); } /* Default: make config name match config filename. */ if (!booth_conf->name[0]) { cp = strrchr(path, '/'); cp = cp ? cp+1 : (char *)path; cp2 = strrchr(cp, '.'); if (!cp2) cp2 = cp + strlen(cp); if (cp2-cp >= BOOTH_NAME_LEN) { log_error("booth config file name too long"); goto out; } strncpy(booth_conf->name, cp, cp2-cp); *(booth_conf->name+(cp2-cp)) = '\0'; } if (!postproc_ticket(current_tk)) { goto out; } poll_timeout = min(POLL_TIMEOUT, min_timeout/10); if (!poll_timeout) poll_timeout = POLL_TIMEOUT; return 0; err: fclose(fp); out: log_error("%s in config file line %d", error, lineno); free(booth_conf); booth_conf = NULL; return -1; } int check_config(int type) { struct passwd *pw; struct group *gr; char *cp, *input; if (!booth_conf) return -1; input = (type == ARBITRATOR) ? booth_conf->arb_user : booth_conf->site_user; if (!*input) goto u_inval; if (isdigit(input[0])) { booth_conf->uid = strtol(input, &cp, 0); if (*cp != 0) { u_inval: log_error("User \"%s\" cannot be resolved into a UID.", input); return ENOENT; } } else { pw = getpwnam(input); if (!pw) goto u_inval; booth_conf->uid = pw->pw_uid; } input = (type == ARBITRATOR) ? booth_conf->arb_group : booth_conf->site_group; if (!*input) goto g_inval; if (isdigit(input[0])) { booth_conf->gid = strtol(input, &cp, 0); if (*cp != 0) { g_inval: log_error("Group \"%s\" cannot be resolved into a UID.", input); return ENOENT; } } else { gr = getgrnam(input); if (!gr) goto g_inval; booth_conf->gid = gr->gr_gid; } return 0; } static int get_other_site(struct booth_site **node) { struct booth_site *n; int i; *node = NULL; if (!booth_conf) return 0; for (i = 0; i < booth_conf->site_count; i++) { n = booth_conf->site + i; if (n != local && n->type == SITE) { if (!*node) { *node = n; } else { return 0; } } } return !*node ? 0 : 1; } int find_site_by_name(char *site, struct booth_site **node, int any_type) { struct booth_site *n; int i; if (!booth_conf) return 0; if (!strcmp(site, OTHER_SITE)) return get_other_site(node); for (i = 0; i < booth_conf->site_count; i++) { n = booth_conf->site + i; if ((n->type == SITE || any_type) && strncmp(n->addr_string, site, sizeof(n->addr_string)) == 0) { *node = n; return 1; } } return 0; } int find_site_by_id(uint32_t site_id, struct booth_site **node) { struct booth_site *n; int i; if (site_id == NO_ONE) { *node = no_leader; return 1; } if (!booth_conf) return 0; for (i = 0; i < booth_conf->site_count; i++) { n = booth_conf->site + i; if (n->site_id == site_id) { *node = n; return 1; } } return 0; } const char *type_to_string(int type) { switch (type) { case ARBITRATOR: return "arbitrator"; case SITE: return "site"; case CLIENT: return "client"; case GEOSTORE: return "attr"; } return "??invalid-type??"; } diff --git a/src/main.c b/src/main.c index 12085f4..71932fa 100644 --- a/src/main.c +++ b/src/main.c @@ -1,1679 +1,1692 @@ /* * Copyright (C) 2011 Jiaju Zhang * Copyright (C) 2013-2014 Philipp Marek * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "b_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#if HAVE_LIBGNUTLS +#include +#endif #if HAVE_LIBGCRYPT #include #endif #ifndef NAMETAG_LIBSYSTEMD #include #else #include "alt/nametag_libsystemd.h" #endif #ifdef COREDUMP_NURSING #include #include #endif #include "log.h" #include "booth.h" #include "config.h" #include "transport.h" #include "inline-fn.h" #include "pacemaker.h" #include "ticket.h" #include "request.h" #include "attr.h" #include "handler.h" #define RELEASE_STR VERSION #define CLIENT_NALLOC 32 static int daemonize = 1; int enable_stderr = 0; timetype start_time; /** Structure for "clients". * Filehandles with incoming data get registered here (and in pollfds), * along with their callbacks. * Because these can be reallocated with every new fd, addressing * happens _only_ by their numeric index. */ struct client *clients = NULL; struct pollfd *pollfds = NULL; static int client_maxi; static int client_size = 0; static const struct booth_site _no_leader = { .addr_string = "none", .site_id = NO_ONE, .index = -1, }; struct booth_site *const no_leader = (struct booth_site*) &_no_leader; typedef enum { BOOTHD_STARTED=0, BOOTHD_STARTING } BOOTH_DAEMON_STATE; int poll_timeout; struct booth_config *booth_conf; struct command_line cl; /* * Global signal handlers variables */ static int sig_exit_handler_called = 0; static int sig_exit_handler_sig = 0; static int sig_usr1_handler_called = 0; static int sig_chld_handler_called = 0; static void client_alloc(void) { int i; if (!(clients = realloc( clients, (client_size + CLIENT_NALLOC) * sizeof(*clients)) ) || !(pollfds = realloc( pollfds, (client_size + CLIENT_NALLOC) * sizeof(*pollfds)) )) { log_error("can't alloc for client array"); exit(1); } for (i = client_size; i < client_size + CLIENT_NALLOC; i++) { clients[i].workfn = NULL; clients[i].deadfn = NULL; clients[i].fd = -1; pollfds[i].fd = -1; pollfds[i].revents = 0; } client_size += CLIENT_NALLOC; } static void client_dead(int ci) { struct client *c = clients + ci; if (c->fd != -1) { log_debug("removing client %d", c->fd); close(c->fd); } c->fd = -1; c->workfn = NULL; if (c->msg) { free(c->msg); c->msg = NULL; c->offset = 0; } pollfds[ci].fd = -1; } int client_add(int fd, const struct booth_transport *tpt, void (*workfn)(int ci), void (*deadfn)(int ci)) { int i; struct client *c; if (client_size - 1 <= client_maxi ) { client_alloc(); } for (i = 0; i < client_size; i++) { c = clients + i; if (c->fd != -1) continue; c->workfn = workfn; if (deadfn) c->deadfn = deadfn; else c->deadfn = client_dead; c->transport = tpt; c->fd = fd; c->msg = NULL; c->offset = 0; pollfds[i].fd = fd; pollfds[i].events = POLLIN; if (i > client_maxi) client_maxi = i; return i; } assert(!"no client"); } int find_client_by_fd(int fd) { int i; if (fd < 0) return -1; for (i = 0; i <= client_maxi; i++) { if (clients[i].fd == fd) return i; } return -1; } static int format_peers(char **pdata, unsigned int *len) { struct booth_site *s; char *data, *cp; char time_str[64]; int i, alloc; *pdata = NULL; *len = 0; alloc = booth_conf->site_count * (BOOTH_NAME_LEN + 256); data = malloc(alloc); if (!data) return -ENOMEM; cp = data; foreach_node(i, s) { if (s == local) continue; strftime(time_str, sizeof(time_str), "%F %T", localtime(&s->last_recv)); cp += snprintf(cp, alloc - (cp - data), "%-12s %s, last recv: %s\n", type_to_string(s->type), s->addr_string, time_str); cp += snprintf(cp, alloc - (cp - data), "\tSent pkts:%u error:%u resends:%u\n", s->sent_cnt, s->sent_err_cnt, s->resend_cnt); cp += snprintf(cp, alloc - (cp - data), "\tRecv pkts:%u error:%u authfail:%u invalid:%u\n\n", s->recv_cnt, s->recv_err_cnt, s->sec_cnt, s->invalid_cnt); if (alloc - (cp - data) <= 0) { free(data); return -ENOMEM; } } *pdata = data; *len = cp - data; return 0; } void list_peers(int fd) { char *data; unsigned int olen; struct boothc_hdr_msg hdr; if (format_peers(&data, &olen) < 0) goto out; init_header(&hdr.header, CL_LIST, 0, 0, RLT_SUCCESS, 0, sizeof(hdr) + olen); (void)send_header_plus(fd, &hdr, data, olen); out: if (data) free(data); } /* trim trailing spaces if the key is ascii */ static void trim_key() { char *p; int i; for (i=0, p=booth_conf->authkey; i < booth_conf->authkey_len; i++, p++) if (!isascii(*p)) return; p = booth_conf->authkey; while (booth_conf->authkey_len > 0 && isspace(*p)) { p++; booth_conf->authkey_len--; } memmove(booth_conf->authkey, p, booth_conf->authkey_len); p = booth_conf->authkey + booth_conf->authkey_len - 1; while (booth_conf->authkey_len > 0 && isspace(*p)) { booth_conf->authkey_len--; p--; } } static int read_authkey() { int fd; booth_conf->authkey[0] = '\0'; fd = open(booth_conf->authfile, O_RDONLY); if (fd < 0) { log_error("cannot open %s: %s", booth_conf->authfile, strerror(errno)); return -1; } if (fstat(fd, &booth_conf->authstat) < 0) { log_error("cannot stat authentication file %s (%d): %s", booth_conf->authfile, fd, strerror(errno)); close(fd); return -1; } if (booth_conf->authstat.st_mode & (S_IRGRP | S_IROTH)) { log_error("%s: file shall not be readable for anyone but the owner", booth_conf->authfile); close(fd); return -1; } booth_conf->authkey_len = read(fd, booth_conf->authkey, BOOTH_MAX_KEY_LEN); close(fd); trim_key(); log_debug("read key of size %d in authfile %s", booth_conf->authkey_len, booth_conf->authfile); /* make sure that the key is of minimum length */ return (booth_conf->authkey_len >= BOOTH_MIN_KEY_LEN) ? 0 : -1; } int update_authkey() { struct stat statbuf; if (stat(booth_conf->authfile, &statbuf) < 0) { log_error("cannot stat authentication file %s: %s", booth_conf->authfile, strerror(errno)); return -1; } if (statbuf.st_mtime > booth_conf->authstat.st_mtime) { return read_authkey(); } return 0; } static int setup_config(int type) { int rv; rv = read_config(cl.configfile, type); if (rv < 0) goto out; if (booth_conf->authfile[0] != '\0') { rv = read_authkey(); if (rv < 0) goto out; #if HAVE_LIBGCRYPT if (!gcry_check_version(NULL)) { log_error("gcry_check_version"); rv = -ENOENT; goto out; } gcry_control(GCRYCTL_DISABLE_SECMEM, 0); gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); +#endif +#if HAVE_LIBGNUTLS + if (gnutls_global_init() != 0) { + log_error("Cannot initialize GnuTLS"); + rv = -EINVAL; + goto out; + }; #endif } /* Set "local" pointer, ignoring errors. */ if (cl.type == DAEMON && cl.site[0]) { if (!find_site_by_name(cl.site, &local, 1)) { log_error("Cannot find \"%s\" in the configuration.", cl.site); return -EINVAL; } local->local = 1; } else find_myself(NULL, type == CLIENT || type == GEOSTORE); rv = check_config(type); if (rv < 0) goto out; /* Per default the PID file name is derived from the * configuration name. */ if (!cl.lockfile[0]) { snprintf(cl.lockfile, sizeof(cl.lockfile)-1, "%s/%s.pid", BOOTH_RUN_DIR, booth_conf->name); } out: return rv; } static int setup_transport(void) { int rv; rv = transport()->init(message_recv); if (rv < 0) { log_error("failed to init booth_transport %s", transport()->name); goto out; } rv = booth_transport[TCP].init(NULL); if (rv < 0) { log_error("failed to init booth_transport[TCP]"); goto out; } out: return rv; } static int write_daemon_state(int fd, int state) { char buffer[1024]; int rv, size; size = sizeof(buffer) - 1; rv = snprintf(buffer, size, "booth_pid=%d " "booth_state=%s " "booth_type=%s " "booth_cfg_name='%s' " "booth_id=%d " "booth_addr_string='%s' " "booth_port=%d\n", getpid(), ( state == BOOTHD_STARTED ? "started" : state == BOOTHD_STARTING ? "starting" : "invalid"), type_to_string(local->type), booth_conf->name, local->site_id, local->addr_string, booth_conf->port); if (rv < 0 || rv == size) { log_error("Buffer filled up in write_daemon_state()."); return -1; } size = rv; rv = ftruncate(fd, 0); if (rv < 0) { log_error("lockfile %s truncate error %d: %s", cl.lockfile, errno, strerror(errno)); return rv; } rv = lseek(fd, 0, SEEK_SET); if (rv < 0) { log_error("lseek set fd(%d) offset to 0 error, return(%d), message(%s)", fd, rv, strerror(errno)); rv = -1; return rv; } rv = write(fd, buffer, size); if (rv != size) { log_error("write to fd(%d, %d) returned %d, errno %d, message(%s)", fd, size, rv, errno, strerror(errno)); return -1; } return 0; } static int process_signals(void) { if (sig_exit_handler_called) { log_info("caught signal %d", sig_exit_handler_sig); return 1; } if (sig_usr1_handler_called) { sig_usr1_handler_called = 0; tickets_log_info(); } if (sig_chld_handler_called) { sig_chld_handler_called = 0; wait_child(SIGCHLD); } return 0; } static int loop(int fd) { void (*workfn) (int ci); void (*deadfn) (int ci); int rv, i; rv = setup_transport(); if (rv < 0) goto fail; rv = setup_ticket(); if (rv < 0) goto fail; rv = write_daemon_state(fd, BOOTHD_STARTED); if (rv != 0) { log_error("write daemon state %d to lockfile error %s: %s", BOOTHD_STARTED, cl.lockfile, strerror(errno)); goto fail; } log_info("BOOTH %s daemon started, node id is 0x%08X (%d).", type_to_string(local->type), local->site_id, local->site_id); while (1) { rv = poll(pollfds, client_maxi + 1, poll_timeout); if (rv == -1 && errno == EINTR) continue; if (rv < 0) { log_error("poll failed: %s (%d)", strerror(errno), errno); goto fail; } for (i = 0; i <= client_maxi; i++) { if (clients[i].fd < 0) continue; if (pollfds[i].revents & POLLIN) { workfn = clients[i].workfn; if (workfn) workfn(i); } if (pollfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { deadfn = clients[i].deadfn; if (deadfn) deadfn(i); } } process_tickets(); if (process_signals() != 0) { return 0; } } return 0; fail: return -1; } static int test_reply(cmd_result_t reply_code, cmd_request_t cmd) { int rv = 0; const char *op_str = ""; if (cmd == CMD_GRANT) op_str = "grant"; else if (cmd == CMD_REVOKE) op_str = "revoke"; else if (cmd == CMD_LIST) op_str = "list"; else if (cmd == CMD_PEERS) op_str = "peers"; else { log_error("internal error reading reply result!"); return -1; } switch (reply_code) { case RLT_OVERGRANT: log_info("You're granting a granted ticket. " "If you wanted to migrate a ticket, " "use revoke first, then use grant."); rv = -1; break; case RLT_TICKET_IDLE: log_info("ticket is not owned"); rv = 0; break; case RLT_ASYNC: log_info("%s command sent, result will be returned " "asynchronously. Please use \"booth list\" to " "see the outcome.", op_str); rv = 0; break; case RLT_CIB_PENDING: log_info("%s succeeded (CIB commit pending)", op_str); /* wait for the CIB commit? */ rv = (cl.options & OPT_WAIT_COMMIT) ? 3 : 0; break; case RLT_MORE: rv = 2; break; case RLT_SYNC_SUCC: case RLT_SUCCESS: if (cmd != CMD_LIST && cmd != CMD_PEERS) log_info("%s succeeded!", op_str); rv = 0; break; case RLT_SYNC_FAIL: log_info("%s failed!", op_str); rv = -1; break; case RLT_INVALID_ARG: log_error("ticket \"%s\" does not exist", cl.msg.ticket.id); rv = -1; break; case RLT_AUTH: log_error("authentication error"); rv = -1; break; case RLT_EXT_FAILED: log_error("before-acquire-handler for ticket \"%s\" failed, grant denied", cl.msg.ticket.id); rv = -1; break; case RLT_ATTR_PREREQ: log_error("attr-prereq for ticket \"%s\" failed, grant denied", cl.msg.ticket.id); rv = -1; break; case RLT_REDIRECT: /* talk to another site */ rv = 1; break; default: log_error("got an error code: %x", rv); rv = -1; } return rv; } static int query_get_string_answer(cmd_request_t cmd) { struct booth_site *site; struct boothc_hdr_msg reply; struct boothc_header *header; char *data; int data_len; int rv; struct booth_transport const *tpt; int (*test_reply_f) (cmd_result_t reply_code, cmd_request_t cmd); size_t msg_size; void *request; if (cl.type == GEOSTORE) { test_reply_f = test_attr_reply; msg_size = sizeof(cl.attr_msg); request = &cl.attr_msg; } else { test_reply_f = test_reply; msg_size = sizeof(cl.msg); request = &cl.msg; } header = (struct boothc_header *)request; data = NULL; init_header(header, cmd, 0, cl.options, 0, 0, msg_size); if (!*cl.site) site = local; else if (!find_site_by_name(cl.site, &site, 1)) { log_error("cannot find site \"%s\"", cl.site); rv = ENOENT; goto out; } tpt = booth_transport + TCP; rv = tpt->open(site); if (rv < 0) goto out_close; rv = tpt->send(site, request, msg_size); if (rv < 0) goto out_close; rv = tpt->recv_auth(site, &reply, sizeof(reply)); if (rv < 0) goto out_close; data_len = ntohl(reply.header.length) - rv; /* no attribute, or no ticket found */ if (!data_len) { goto out_test_reply; } data = malloc(data_len+1); if (!data) { rv = -ENOMEM; goto out_close; } rv = tpt->recv(site, data, data_len); if (rv < 0) goto out_close; *(data + data_len) = '\0'; (void)fputs(data, stdout); fflush(stdout); rv = 0; out_test_reply: rv = test_reply_f(ntohl(reply.header.result), cmd); out_close: tpt->close(site); out: if (data) free(data); return rv; } static int do_command(cmd_request_t cmd) { struct booth_site *site; struct boothc_ticket_msg reply; struct booth_transport const *tpt; uint32_t leader_id; int rv; int reply_cnt = 0, msg_logged = 0; const char *op_str = ""; if (cmd == CMD_GRANT) op_str = "grant"; else if (cmd == CMD_REVOKE) op_str = "revoke"; rv = -1; site = NULL; /* Always use TCP for client - at least for now. */ tpt = booth_transport + TCP; if (!*cl.site) site = local; else { if (!find_site_by_name(cl.site, &site, 1)) { log_error("Site \"%s\" not configured.", cl.site); goto out_close; } } if (site->type == ARBITRATOR) { if (site == local) { log_error("We're just an arbitrator, cannot grant/revoke tickets here."); } else { log_error("%s is just an arbitrator, cannot grant/revoke tickets there.", cl.site); } goto out_close; } assert(site->type == SITE); /* We don't check for existence of ticket, so that asking can be * done without local configuration, too. * Although, that means that the UDP port has to be specified, too. */ if (!cl.msg.ticket.id[0]) { /* If the loaded configuration has only a single ticket defined, use that. */ if (booth_conf->ticket_count == 1) { strncpy(cl.msg.ticket.id, booth_conf->ticket[0].name, sizeof(cl.msg.ticket.id)); } else { log_error("No ticket given."); goto out_close; } } redirect: init_header(&cl.msg.header, cmd, 0, cl.options, 0, 0, sizeof(cl.msg)); rv = tpt->open(site); if (rv < 0) goto out_close; rv = tpt->send(site, &cl.msg, sendmsglen(&cl.msg)); if (rv < 0) goto out_close; read_more: rv = tpt->recv_auth(site, &reply, sizeof(reply)); if (rv < 0) { /* print any errors depending on the code sent by the * server */ (void)test_reply(ntohl(reply.header.result), cmd); goto out_close; } rv = test_reply(ntohl(reply.header.result), cmd); if (rv == 1) { tpt->close(site); leader_id = ntohl(reply.ticket.leader); if (!find_site_by_id(leader_id, &site)) { log_error("Message with unknown redirect site %x received", leader_id); rv = -1; goto out_close; } goto redirect; } else if (rv == 2 || rv == 3) { /* the server has more to say */ /* don't wait too long */ if (reply_cnt > 1 && !(cl.options & OPT_WAIT)) { rv = 0; log_info("Giving up on waiting for the definite result. " "Please use \"booth list\" later to " "see the outcome."); goto out_close; } if (reply_cnt == 0) { log_info("%s request sent, " "waiting for the result ...", op_str); msg_logged++; } else if (rv == 3 && msg_logged < 2) { log_info("waiting for the CIB commit ..."); msg_logged++; } reply_cnt++; goto read_more; } out_close: if (site) tpt->close(site); return rv; } static int _lockfile(int mode, int *fdp, pid_t *locked_by) { struct flock lock; int fd, rv; /* After reboot the directory may not yet exist. * Try to create it, but ignore errors. */ if (strncmp(cl.lockfile, BOOTH_RUN_DIR, strlen(BOOTH_RUN_DIR)) == 0) (void)mkdir(BOOTH_RUN_DIR, 0775); if (locked_by) *locked_by = 0; *fdp = -1; fd = open(cl.lockfile, mode, 0664); if (fd < 0) return errno; *fdp = fd; lock.l_type = F_WRLCK; lock.l_start = 0; lock.l_whence = SEEK_SET; lock.l_len = 0; lock.l_pid = 0; if (fcntl(fd, F_SETLK, &lock) == 0) return 0; rv = errno; if (locked_by) if (fcntl(fd, F_GETLK, &lock) == 0) *locked_by = lock.l_pid; return rv; } static inline int is_root(void) { return geteuid() == 0; } static int create_lockfile(void) { int rv, fd; fd = -1; rv = _lockfile(O_CREAT | O_WRONLY, &fd, NULL); if (fd == -1) { log_error("lockfile %s open error %d: %s", cl.lockfile, rv, strerror(rv)); return -1; } if (rv < 0) { log_error("lockfile %s setlk error %d: %s", cl.lockfile, rv, strerror(rv)); goto fail; } rv = write_daemon_state(fd, BOOTHD_STARTING); if (rv != 0) { log_error("write daemon state %d to lockfile error %s: %s", BOOTHD_STARTING, cl.lockfile, strerror(errno)); goto fail; } if (is_root()) { if (fchown(fd, booth_conf->uid, booth_conf->gid) < 0) log_error("fchown() on lockfile said %d: %s", errno, strerror(errno)); } return fd; fail: close(fd); return -1; } static void unlink_lockfile(int fd) { unlink(cl.lockfile); close(fd); } static void print_usage(void) { printf( "Usage:\n" " booth list [options]\n" " booth {grant|revoke} [options] \n" " booth status [options]\n" "\n" " list: List all tickets\n" " grant: Grant ticket to site\n" " revoke: Revoke ticket\n" "\n" "Options:\n" " -c FILE Specify config file [default " BOOTH_DEFAULT_CONF "]\n" " Can be a path or just a name without \".conf\" suffix\n" " -s Connect/grant to a different site\n" " -F Try to grant the ticket immediately\n" " even if not all sites are reachable\n" " For manual tickets:\n" " grant a manual ticket even if it has been already granted\n" " -w Wait forever for the outcome of the request\n" " -C Wait until the ticket is committed to the CIB (grant only)\n" " -h Print this help\n" "\n" "Examples:\n" "\n" " # booth list (list tickets)\n" " # booth grant ticket-A (grant ticket here)\n" " # booth grant -s 10.121.8.183 ticket-A (grant ticket to site 10.121.8.183)\n" " # booth revoke ticket-A (revoke ticket)\n" "\n" "See the booth(8) man page for more details.\n" ); } #define OPTION_STRING "c:Dl:t:s:FhSwC" #define ATTR_OPTION_STRING "c:Dt:s:h" void safe_copy(char *dest, char *value, size_t buflen, const char *description) { int content_len = buflen - 1; if (strlen(value) >= content_len) { fprintf(stderr, "'%s' exceeds maximum %s length of %d\n", value, description, content_len); exit(EXIT_FAILURE); } strncpy(dest, value, content_len); dest[content_len] = 0; } static int host_convert(char *hostname, char *ip_str, size_t ip_size) { struct addrinfo *result = NULL, hints = {0}; int re = -1; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; re = getaddrinfo(hostname, NULL, &hints, &result); if (re == 0) { struct in_addr addr = ((struct sockaddr_in *)result->ai_addr)->sin_addr; const char *re_ntop = inet_ntop(AF_INET, &addr, ip_str, ip_size); if (re_ntop == NULL) { re = -1; } } freeaddrinfo(result); return re; } #define cparg(dest, descr) do { \ if (optind >= argc) \ goto missingarg; \ safe_copy(dest, argv[optind], sizeof(dest), descr); \ optind++; \ } while(0) static int read_arguments(int argc, char **argv) { int optchar; char *arg1 = argv[1]; char *op = NULL; char *cp; const char *opt_string = OPTION_STRING; char site_arg[INET_ADDRSTRLEN] = {0}; int left; cl.type = 0; if ((cp = strstr(argv[0], ATTR_PROG)) && !strcmp(cp, ATTR_PROG)) { cl.type = GEOSTORE; op = argv[1]; optind = 2; opt_string = ATTR_OPTION_STRING; } else if (argc > 1 && (strcmp(arg1, "arbitrator") == 0 || strcmp(arg1, "site") == 0 || strcmp(arg1, "start") == 0 || strcmp(arg1, "daemon") == 0)) { cl.type = DAEMON; optind = 2; } else if (argc > 1 && (strcmp(arg1, "status") == 0)) { cl.type = STATUS; optind = 2; } else if (argc > 1 && (strcmp(arg1, "client") == 0)) { cl.type = CLIENT; if (argc < 3) { print_usage(); exit(EXIT_FAILURE); } op = argv[2]; optind = 3; } if (!cl.type) { cl.type = CLIENT; op = argv[1]; optind = 2; } if (argc < 2 || !strcmp(arg1, "help") || !strcmp(arg1, "--help") || !strcmp(arg1, "-h")) { if (cl.type == GEOSTORE) print_geostore_usage(); else print_usage(); exit(EXIT_SUCCESS); } if (!strcmp(arg1, "version") || !strcmp(arg1, "--version") || !strcmp(arg1, "-V")) { printf("%s %s\n", argv[0], RELEASE_STR); exit(EXIT_SUCCESS); } if (cl.type == CLIENT) { if (!strcmp(op, "list")) cl.op = CMD_LIST; else if (!strcmp(op, "grant")) cl.op = CMD_GRANT; else if (!strcmp(op, "revoke")) cl.op = CMD_REVOKE; else if (!strcmp(op, "peers")) cl.op = CMD_PEERS; else { fprintf(stderr, "client operation \"%s\" is unknown\n", op); exit(EXIT_FAILURE); } } else if (cl.type == GEOSTORE) { if (!strcmp(op, "list")) cl.op = ATTR_LIST; else if (!strcmp(op, "set")) cl.op = ATTR_SET; else if (!strcmp(op, "get")) cl.op = ATTR_GET; else if (!strcmp(op, "delete")) cl.op = ATTR_DEL; else { fprintf(stderr, "attribute operation \"%s\" is unknown\n", op); exit(EXIT_FAILURE); } } while (optind < argc) { optchar = getopt(argc, argv, opt_string); switch (optchar) { case 'c': if (strchr(optarg, '/')) { safe_copy(cl.configfile, optarg, sizeof(cl.configfile), "config file"); } else { /* If no "/" in there, use with default directory. */ strcpy(cl.configfile, BOOTH_DEFAULT_CONF_DIR); cp = cl.configfile + strlen(BOOTH_DEFAULT_CONF_DIR); assert(cp > cl.configfile); assert(*(cp-1) == '/'); /* Write at the \0, ie. after the "/" */ safe_copy(cp, optarg, (sizeof(cl.configfile) - (cp - cl.configfile) - strlen(BOOTH_DEFAULT_CONF_EXT)), "config name"); /* If no extension, append ".conf". * Space is available, see -strlen() above. */ if (!strchr(cp, '.')) strcat(cp, BOOTH_DEFAULT_CONF_EXT); } break; case 'D': debug_level++; break; case 'S': daemonize = 0; enable_stderr = 1; break; case 'l': safe_copy(cl.lockfile, optarg, sizeof(cl.lockfile), "lock file"); break; case 't': if (cl.op == CMD_GRANT || cl.op == CMD_REVOKE) { safe_copy(cl.msg.ticket.id, optarg, sizeof(cl.msg.ticket.id), "ticket name"); } else if (cl.type == GEOSTORE) { safe_copy(cl.attr_msg.attr.tkt_id, optarg, sizeof(cl.attr_msg.attr.tkt_id), "ticket name"); } else { print_usage(); exit(EXIT_FAILURE); } break; case 's': /* For testing and debugging: allow "-s site" also for * daemon start, so that the address that should be used * can be set manually. * This makes it easier to start multiple processes * on one machine. */ if (cl.type == CLIENT || cl.type == GEOSTORE || (cl.type == DAEMON && debug_level)) { if (strcmp(optarg, OTHER_SITE) && host_convert(optarg, site_arg, INET_ADDRSTRLEN) == 0) { safe_copy(cl.site, site_arg, sizeof(cl.site), "site name"); } else { safe_copy(cl.site, optarg, sizeof(cl.site), "site name"); } } else { log_error("\"-s\" not allowed in daemon mode."); exit(EXIT_FAILURE); } break; case 'F': if (cl.type != CLIENT || cl.op != CMD_GRANT) { log_error("use \"-F\" only for client grant"); exit(EXIT_FAILURE); } cl.options |= OPT_IMMEDIATE; break; case 'w': if (cl.type != CLIENT || (cl.op != CMD_GRANT && cl.op != CMD_REVOKE)) { log_error("use \"-w\" only for grant and revoke"); exit(EXIT_FAILURE); } cl.options |= OPT_WAIT; break; case 'C': if (cl.type != CLIENT || cl.op != CMD_GRANT) { log_error("use \"-C\" only for grant"); exit(EXIT_FAILURE); } cl.options |= OPT_WAIT | OPT_WAIT_COMMIT; break; case 'h': if (cl.type == GEOSTORE) print_geostore_usage(); else print_usage(); exit(EXIT_SUCCESS); break; case ':': case '?': fprintf(stderr, "Please use '-h' for usage.\n"); exit(EXIT_FAILURE); break; case -1: /* No more parameters on cmdline, only arguments. */ goto extra_args; default: goto unknown; }; } return 0; extra_args: if (cl.type == CLIENT && !cl.msg.ticket.id[0]) { cparg(cl.msg.ticket.id, "ticket name"); } else if (cl.type == GEOSTORE) { if (cl.op != ATTR_LIST) { cparg(cl.attr_msg.attr.name, "attribute name"); } if (cl.op == ATTR_SET) { cparg(cl.attr_msg.attr.val, "attribute value"); } } if (optind == argc) return 0; left = argc - optind; fprintf(stderr, "Superfluous argument%s: %s%s\n", left == 1 ? "" : "s", argv[optind], left == 1 ? "" : "..."); exit(EXIT_FAILURE); unknown: fprintf(stderr, "unknown option: %s\n", argv[optind]); exit(EXIT_FAILURE); missingarg: fprintf(stderr, "not enough arguments\n"); exit(EXIT_FAILURE); } static void set_scheduler(void) { struct sched_param sched_param; struct rlimit rlimit; int rv; rlimit.rlim_cur = RLIM_INFINITY; rlimit.rlim_max = RLIM_INFINITY; rv = setrlimit(RLIMIT_MEMLOCK, &rlimit); if (rv < 0) { log_error("setrlimit failed"); } else { rv = mlockall(MCL_CURRENT | MCL_FUTURE); if (rv < 0) { log_error("mlockall failed"); } } rv = sched_get_priority_max(SCHED_RR); if (rv != -1) { sched_param.sched_priority = rv; rv = sched_setscheduler(0, SCHED_RR, &sched_param); if (rv == -1) log_error("could not set SCHED_RR priority %d: %s (%d)", sched_param.sched_priority, strerror(errno), errno); } else { log_error("could not get maximum scheduler priority err %d", errno); } } static int set_procfs_val(const char *path, const char *val) { int rc = -1; FILE *fp = fopen(path, "w"); if (fp) { if (fprintf(fp, "%s", val) > 0) rc = 0; fclose(fp); } return rc; } static int do_status(int type) { pid_t pid; int rv, status_lock_fd, ret; const char *reason = NULL; char lockfile_data[1024], *cp; ret = PCMK_OCF_NOT_RUNNING; rv = setup_config(type); if (rv) { reason = "Error reading configuration."; ret = PCMK_OCF_UNKNOWN_ERROR; goto quit; } if (!local) { reason = "No Service IP active here."; goto quit; } rv = _lockfile(O_RDWR, &status_lock_fd, &pid); if (status_lock_fd == -1) { reason = "No PID file."; goto quit; } if (rv == 0) { close(status_lock_fd); reason = "PID file not locked."; goto quit; } if (pid) { fprintf(stdout, "booth_lockpid=%d ", pid); fflush(stdout); } rv = read(status_lock_fd, lockfile_data, sizeof(lockfile_data) - 1); if (rv < 4) { close(status_lock_fd); reason = "Cannot read lockfile data."; ret = PCMK_LSB_UNKNOWN_ERROR; goto quit; } lockfile_data[rv] = 0; close(status_lock_fd); /* Make sure it's only a single line */ cp = strchr(lockfile_data, '\r'); if (cp) *cp = 0; cp = strchr(lockfile_data, '\n'); if (cp) *cp = 0; rv = setup_tcp_listener(1); if (rv == 0) { reason = "TCP port not in use."; goto quit; } fprintf(stdout, "booth_lockfile='%s' %s\n", cl.lockfile, lockfile_data); if (!daemonize) fprintf(stderr, "Booth at %s port %d seems to be running.\n", local->addr_string, booth_conf->port); return 0; quit: log_debug("not running: %s", reason); /* Ie. "DEBUG" */ if (!daemonize) fprintf(stderr, "not running: %s\n", reason); return ret; } static int limit_this_process(void) { int rv; if (!is_root()) return 0; if (setregid(booth_conf->gid, booth_conf->gid) < 0) { rv = errno; log_error("setregid() didn't work: %s", strerror(rv)); return rv; } if (setreuid(booth_conf->uid, booth_conf->uid) < 0) { rv = errno; log_error("setreuid() didn't work: %s", strerror(rv)); return rv; } return 0; } static int lock_fd = -1; static void server_exit(void) { int rv; if (lock_fd >= 0) { /* We might not be able to delete it, but at least * make it empty. */ rv = ftruncate(lock_fd, 0); (void)rv; unlink_lockfile(lock_fd); } log_info("exiting"); } static void sig_exit_handler(int sig) { sig_exit_handler_sig = sig; sig_exit_handler_called = 1; } static void sig_usr1_handler(int sig) { sig_usr1_handler_called = 1; } static void sig_chld_handler(int sig) { sig_chld_handler_called = 1; } static int do_server(int type) { int rv = -1; static char log_ent[128] = DAEMON_NAME "-"; rv = setup_config(type); if (rv < 0) return rv; if (!local) { log_error("Cannot find myself in the configuration."); exit(EXIT_FAILURE); } if (daemonize) { if (daemon(0, 0) < 0) { perror("daemon error"); exit(EXIT_FAILURE); } } /* * Register signal and exit handler */ signal(SIGUSR1, (__sighandler_t)sig_usr1_handler); signal(SIGTERM, (__sighandler_t)sig_exit_handler); signal(SIGINT, (__sighandler_t)sig_exit_handler); /* we'll handle errors there and then */ signal(SIGPIPE, SIG_IGN); atexit(server_exit); /* The lockfile must be written to _after_ the call to daemon(), so * that the lockfile contains the pid of the daemon, not the parent. */ lock_fd = create_lockfile(); if (lock_fd < 0) return lock_fd; strcat(log_ent, type_to_string(local->type)); cl_log_set_entity(log_ent); cl_log_enable_stderr(enable_stderr ? TRUE : FALSE); cl_log_set_facility(HA_LOG_FACILITY); cl_inherit_logging_environment(0); log_info("BOOTH %s %s daemon is starting", type_to_string(local->type), RELEASE_STR); set_scheduler(); /* we don't want to be killed by the OOM-killer */ if (set_procfs_val("/proc/self/oom_score_adj", "-999")) (void)set_procfs_val("/proc/self/oom_adj", "-16"); set_proc_title("%s %s %s for [%s]:%d", DAEMON_NAME, cl.configfile, type_to_string(local->type), local->addr_string, booth_conf->port); rv = limit_this_process(); if (rv) return rv; #ifdef COREDUMP_NURSING if (cl_enable_coredumps(TRUE) < 0){ log_error("enabling core dump failed"); } cl_cdtocoredir(); prctl(PR_SET_DUMPABLE, (unsigned long)TRUE, 0UL, 0UL, 0UL); #else if (chdir(BOOTH_CORE_DIR) < 0) { log_error("cannot change working directory to %s", BOOTH_CORE_DIR); } #endif signal(SIGCHLD, (__sighandler_t)sig_chld_handler); rv = loop(lock_fd); return rv; } static int do_client(void) { int rv; rv = setup_config(CLIENT); if (rv < 0) { log_error("cannot read config"); goto out; } switch (cl.op) { case CMD_LIST: case CMD_PEERS: rv = query_get_string_answer(cl.op); break; case CMD_GRANT: case CMD_REVOKE: rv = do_command(cl.op); break; } out: return rv; } static int do_attr(void) { int rv = -1; rv = setup_config(GEOSTORE); if (rv < 0) { log_error("cannot read config"); goto out; } /* We don't check for existence of ticket, so that asking can be * done without local configuration, too. * Although, that means that the UDP port has to be specified, too. */ if (!cl.attr_msg.attr.tkt_id[0]) { /* If the loaded configuration has only a single ticket defined, use that. */ if (booth_conf->ticket_count == 1) { strncpy(cl.attr_msg.attr.tkt_id, booth_conf->ticket[0].name, sizeof(cl.attr_msg.attr.tkt_id)); } else { rv = 1; log_error("No ticket given."); goto out; } } switch (cl.op) { case ATTR_LIST: case ATTR_GET: rv = query_get_string_answer(cl.op); break; case ATTR_SET: case ATTR_DEL: rv = do_attr_command(cl.op); break; } out: return rv; } int main(int argc, char *argv[], char *envp[]) { int rv; const char *cp; #ifdef LOGGING_LIBQB enum qb_log_target_slot i; #endif init_set_proc_title(argc, argv, envp); get_time(&start_time); memset(&cl, 0, sizeof(cl)); strncpy(cl.configfile, BOOTH_DEFAULT_CONF, BOOTH_PATH_LEN - 1); cl.lockfile[0] = 0; debug_level = 0; cp = ((cp = strstr(argv[0], ATTR_PROG)) && !strcmp(cp, ATTR_PROG) ? ATTR_PROG : "booth"); #ifndef LOGGING_LIBQB cl_log_set_entity(cp); #else qb_log_init(cp, LOG_USER, LOG_DEBUG); /* prio driven by debug_level */ for (i = QB_LOG_TARGET_START; i < QB_LOG_TARGET_MAX; i++) { if (i == QB_LOG_SYSLOG || i == QB_LOG_BLACKBOX) continue; qb_log_format_set(i, "%t %H %N: [%P]: %p: %b"); } (void) qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", LOG_DEBUG); #endif cl_log_enable_stderr(TRUE); cl_log_set_facility(0); rv = read_arguments(argc, argv); if (rv < 0) goto out; switch (cl.type) { case STATUS: rv = do_status(cl.type); break; case ARBITRATOR: case DAEMON: case SITE: rv = do_server(cl.type); break; case CLIENT: rv = do_client(); break; case GEOSTORE: rv = do_attr(); break; } out: +#if HAVE_LIBGNUTLS + gnutls_global_deinit(); +#endif #ifdef LOGGING_LIBQB qb_log_fini(); #endif /* Normalize values. 0x100 would be seen as "OK" by waitpid(). */ return (rv >= 0 && rv < 0x70) ? rv : 1; } diff --git a/src/transport.c b/src/transport.c index 817a4dc..8267c96 100644 --- a/src/transport.c +++ b/src/transport.c @@ -1,1143 +1,1143 @@ /* * Copyright (C) 2011 Jiaju Zhang * Copyright (C) 2013-2014 Philipp Marek * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "b_config.h" #include #include #include #include #include #include #include /* getnameinfo */ #include #include #include #include #include #include #include #include /* getnameinfo */ #include "attr.h" #include "auth.h" #include "booth.h" #include "config.h" #include "inline-fn.h" #include "log.h" #include "ticket.h" #include "transport.h" #define BOOTH_IPADDR_LEN (sizeof(struct in6_addr)) #define NETLINK_BUFSIZE 16384 #define SOCKET_BUFFER_SIZE 160000 #define FRAME_SIZE_MAX 10000 struct booth_site *local = NULL; /* function to be called when handling booth-group-internal messages; * it's expected to return 0 to indicate success, negative integer * to indicate silent (or possibly already complained about) error, * or positive integer to indicate sender's ID that will then be * emitted in the error log message together with the real source * address if this is available */ static int (*deliver_fn) (void *msg, int msglen); static void parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) { while (RTA_OK(rta, len)) { if (rta->rta_type <= max) tb[rta->rta_type] = rta; rta = RTA_NEXT(rta,len); } } enum match_type { NO_MATCH = 0, FUZZY_MATCH, EXACT_MATCH, }; static int find_address(unsigned char ipaddr[BOOTH_IPADDR_LEN], int family, int prefixlen, int fuzzy_allowed, struct booth_site **me, int *address_bits_matched) { int i; struct booth_site *node; int bytes, bits_left, mask; unsigned char node_bits, ip_bits; uint8_t *n_a; int matched; enum match_type did_match = NO_MATCH; bytes = prefixlen / 8; bits_left = prefixlen % 8; /* One bit left to check means ignore 7 lowest bits. */ mask = ~( (1 << (8 - bits_left)) -1); for (i = 0; i < booth_conf->site_count; i++) { node = booth_conf->site + i; if (family != node->family) continue; n_a = node_to_addr_pointer(node); for(matched = 0; matched < node->addrlen; matched++) if (ipaddr[matched] != n_a[matched]) break; if (matched == node->addrlen) { *address_bits_matched = matched * 8; *me = node; did_match = EXACT_MATCH; break; } if (!fuzzy_allowed) continue; /* Check prefix, whole bytes */ if (matched < bytes) continue; if (matched * 8 < *address_bits_matched) continue; node_bits = n_a[bytes]; ip_bits = ipaddr[bytes]; if (((node_bits ^ ip_bits) & mask) == 0) { /* _At_least_ prefixlen bits matched. */ if (did_match < EXACT_MATCH) { *address_bits_matched = prefixlen; *me = node; did_match = FUZZY_MATCH; } } } return did_match; } int _find_myself(int family, struct booth_site **mep, int fuzzy_allowed); int _find_myself(int family, struct booth_site **mep, int fuzzy_allowed) { int fd; struct sockaddr_nl nladdr; struct booth_site *me; unsigned char ipaddr[BOOTH_IPADDR_LEN]; static char rcvbuf[NETLINK_BUFSIZE]; struct { struct nlmsghdr nlh; struct rtgenmsg g; } req; int address_bits_matched; if (local) goto found; me = NULL; address_bits_matched = 0; if (mep) *mep = NULL; fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd < 0) { log_error("failed to create netlink socket"); return 0; } (void)setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = sizeof(req); req.nlh.nlmsg_type = RTM_GETADDR; req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; req.nlh.nlmsg_pid = 0; req.nlh.nlmsg_seq = 1; req.g.rtgen_family = family; if (sendto(fd, (void *)&req, sizeof(req), 0, (struct sockaddr*)&nladdr, sizeof(nladdr)) < 0) { close(fd); log_error("failed to send data to netlink socket"); return 0; } while (1) { int status; struct nlmsghdr *h; struct iovec iov = { rcvbuf, sizeof(rcvbuf) }; struct msghdr msg = { (void *)&nladdr, sizeof(nladdr), &iov, 1, NULL, 0, 0 }; status = recvmsg(fd, &msg, 0); if (!status) { close(fd); log_error("failed to recvmsg from netlink socket"); return 0; } for (h = (struct nlmsghdr *)rcvbuf; NLMSG_OK(h, status); h = NLMSG_NEXT(h, status)) { if (h->nlmsg_type == NLMSG_DONE) goto out; if (h->nlmsg_type == NLMSG_ERROR) { close(fd); log_error("netlink socket recvmsg error"); return 0; } if (h->nlmsg_type == RTM_NEWADDR) { struct ifaddrmsg *ifa = NLMSG_DATA(h); struct rtattr *tb[IFA_MAX+1]; int len = h->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)); memset(tb, 0, sizeof(tb)); parse_rtattr(tb, IFA_MAX, IFA_RTA(ifa), len); memset(ipaddr, 0, BOOTH_IPADDR_LEN); /* prefer IFA_LOCAL if it exists, for p-t-p * interfaces, otherwise use IFA_ADDRESS */ if (tb[IFA_LOCAL]) { memcpy(ipaddr, RTA_DATA(tb[IFA_LOCAL]), BOOTH_IPADDR_LEN); } else { memcpy(ipaddr, RTA_DATA(tb[IFA_ADDRESS]), BOOTH_IPADDR_LEN); } /* Try to find the exact address or the address with subnet matching. * The function find_address will be called for each address received * from NLMSG_DATA above. * The exact match will be preferred. If no exact match is found, * the function find_address will try to return another, most similar * address (with the longest possible number of same bytes). */ if (ifa->ifa_prefixlen > address_bits_matched) { find_address(ipaddr, ifa->ifa_family, ifa->ifa_prefixlen, fuzzy_allowed, &me, &address_bits_matched); if (me) { log_debug("found myself at %s (%d bits matched)", site_string(me), address_bits_matched); } } /* If the previous NLMSG_DATA calls have already allowed us * to find an address with address_bits_matched matching bits, * then no other better non-exact address can be found. * But we can still try to find an exact match, so let us * call the function find_address with disabled searching of * similar addresses (fuzzy_allowed == 0) */ else if (ifa->ifa_prefixlen == address_bits_matched) { find_address(ipaddr, ifa->ifa_family, ifa->ifa_prefixlen, 0 /* fuzzy_allowed */, &me, &address_bits_matched); if (me) { log_debug("found myself at %s (exact match)", site_string(me)); } } } } } out: close(fd); if (!me) return 0; me->local = 1; local = me; found: if (mep) *mep = local; return 1; } int find_myself(struct booth_site **mep, int fuzzy_allowed) { return _find_myself(AF_INET6, mep, fuzzy_allowed) || _find_myself(AF_INET, mep, fuzzy_allowed); } /** Checks the header fields for validity. * cf. init_header(). * For @len_incl_data < 0 the length is not checked. * Return <0 if error, else bytes read. */ int check_boothc_header(struct boothc_header *h, int len_incl_data) { int l; if (h->magic != htonl(BOOTHC_MAGIC)) { log_error("magic error %x", ntohl(h->magic)); return -EINVAL; } if (h->version != htonl(BOOTHC_VERSION)) { log_error("version error %x", ntohl(h->version)); return -EINVAL; } l = ntohl(h->length); if (l < sizeof(*h)) { log_error("length %d out of range", l); return -EINVAL; } if (len_incl_data < 0) return 0; if (l != len_incl_data) { log_error("length error - got %d, wanted %d", len_incl_data, l); return -EINVAL; } return len_incl_data; } static int do_read(int fd, void *buf, size_t count) { int rv, off = 0; while (off < count) { rv = read(fd, (char *)buf + off, count - off); if (rv == 0) return -1; if (rv == -1 && errno == EINTR) continue; if (rv == -1 && errno == EWOULDBLOCK) break; if (rv == -1) return -1; off += rv; } return off; } static int do_write(int fd, void *buf, size_t count) { int rv, off = 0; retry: rv = send(fd, (char *)buf + off, count, MSG_NOSIGNAL); if (rv == -1 && errno == EINTR) goto retry; /* If we cannot write _any_ data, we'd be in an (potential) loop. */ if (rv <= 0) { log_error("send failed: %s (%d)", strerror(errno), errno); return rv; } if (rv != count) { count -= rv; off += rv; goto retry; } return 0; } /* Only used for client requests (tcp) */ int read_client(struct client *req_cl) { char *msg; struct boothc_header *header; int rv, fd; int len = MAX_MSG_LEN; if (!req_cl->msg) { msg = malloc(MAX_MSG_LEN); if (!msg) { log_error("out of memory for client messages"); return -1; } req_cl->msg = (void *)msg; } else { msg = (char *)req_cl->msg; } header = (struct boothc_header *)msg; /* update len if we read enough */ if (req_cl->offset >= sizeof(*header)) { len = min(ntohl(header->length), MAX_MSG_LEN); } fd = req_cl->fd; rv = do_read(fd, msg+req_cl->offset, len-req_cl->offset); if (rv < 0) { if (errno == ECONNRESET) log_debug("client connection reset for fd %d", fd); return -1; } req_cl->offset += rv; /* update len if we read enough */ if (req_cl->offset >= sizeof(*header)) { len = min(ntohl(header->length), MAX_MSG_LEN); } if (req_cl->offset < len) { /* client promised to send more */ return 1; } if (check_boothc_header(header, len) < 0) { return -1; } return 0; } /* Only used for client requests (tcp) */ static void process_connection(int ci) { struct client *req_cl; void *msg = NULL; struct boothc_header *header; struct boothc_hdr_msg err_reply; cmd_result_t errc; void (*deadfn) (int ci); req_cl = clients + ci; switch (read_client(req_cl)) { case -1: /* error */ goto kill; case 1: /* more to read */ return; case 0: /* we can process the request now */ msg = req_cl->msg; } header = (struct boothc_header *)msg; if (check_auth(NULL, msg, ntohl(header->length))) { errc = RLT_AUTH; goto send_err; } /* For CMD_GRANT and CMD_REVOKE: * Don't close connection immediately, but send * result a second later? */ switch (ntohl(header->cmd)) { case CMD_LIST: ticket_answer_list(req_cl->fd); goto kill; case CMD_PEERS: list_peers(req_cl->fd); goto kill; case CMD_GRANT: case CMD_REVOKE: if (process_client_request(req_cl, msg) == 1) goto kill; /* request processed definitely, close connection */ else return; case ATTR_LIST: case ATTR_GET: case ATTR_SET: case ATTR_DEL: if (process_attr_request(req_cl, msg) == 1) goto kill; /* request processed definitely, close connection */ else return; default: log_error("connection %d cmd %x unknown", ci, ntohl(header->cmd)); errc = RLT_INVALID_ARG; goto send_err; } assert(0); return; send_err: init_header(&err_reply.header, CL_RESULT, 0, 0, errc, 0, sizeof(err_reply)); send_client_msg(req_cl->fd, &err_reply); kill: deadfn = req_cl->deadfn; if(deadfn) { deadfn(ci); } return; } static void process_tcp_listener(int ci) { int fd, i, flags, one = 1; socklen_t addrlen = sizeof(struct sockaddr); struct sockaddr addr; fd = accept(clients[ci].fd, &addr, &addrlen); if (fd < 0) { log_error("process_tcp_listener: accept error %d %d", fd, errno); return; } (void)setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one)); flags = fcntl(fd, F_GETFL, 0); if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { log_error("process_tcp_listener: fcntl O_NONBLOCK error %d %d", fd, errno); (void)close(fd); return; } i = client_add(fd, clients[ci].transport, process_connection, NULL); log_debug("client connection %d fd %d", i, fd); } int setup_tcp_listener(int test_only) { int s, rv; int one = 1; s = socket(local->family, SOCK_STREAM, 0); if (s == -1) { log_error("failed to create tcp socket %s", strerror(errno)); return s; } rv = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)); if (rv == -1) { close(s); log_error("failed to set the SO_REUSEADDR option"); return rv; } rv = bind(s, &local->sa6, local->saddrlen); if (test_only) { rv = (rv == -1) ? errno : 0; close(s); return rv; } if (rv == -1) { close(s); log_error("failed to bind socket %s", strerror(errno)); return rv; } rv = listen(s, 5); if (rv == -1) { close(s); log_error("failed to listen on socket %s", strerror(errno)); return rv; } return s; } static int booth_tcp_init(void * unused __attribute__((unused))) { int rv; if (get_local_id() < 0) return -1; rv = setup_tcp_listener(0); if (rv < 0) return rv; client_add(rv, booth_transport + TCP, process_tcp_listener, NULL); return 0; } static int connect_nonb(int sockfd, const struct sockaddr *saptr, socklen_t salen, int sec) { int flags, n, error; socklen_t len; fd_set rset, wset; struct timeval tval; flags = fcntl(sockfd, F_GETFL, 0); if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) { log_error("fcntl: Can't set sockfd to nonblocking mode"); return -1; } error = 0; if ( (n = connect(sockfd, saptr, salen)) < 0) if (errno != EINPROGRESS) return -1; if (n == 0) goto done; /* connect completed immediately */ FD_ZERO(&rset); FD_SET(sockfd, &rset); wset = rset; tval.tv_sec = sec; tval.tv_usec = 0; if ((n = select(sockfd + 1, &rset, &wset, NULL, sec ? &tval : NULL)) == 0) { /* leave outside function to close */ /* timeout */ /* close(sockfd); */ errno = ETIMEDOUT; return -1; } if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) { len = sizeof(error); if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) return -1; /* Solaris pending error */ } else { log_error("select error: sockfd not set"); return -1; } done: /* restore file status flags */ if (fcntl(sockfd, F_SETFL, flags) == -1) { log_error("fcntl: Can't restore sockfd flags"); return -1; } if (error) { /* leave outside function to close */ /* close(sockfd); */ errno = error; return -1; } return 0; } int booth_tcp_open(struct booth_site *to) { int s, rv; if (to->tcp_fd >= STDERR_FILENO) goto found; s = socket(to->family, SOCK_STREAM, 0); if (s == -1) { log_error("cannot create socket of family %d", to->family); return -1; } rv = connect_nonb(s, (struct sockaddr *)&to->sa6, to->saddrlen, 10); if (rv == -1) { if( errno == ETIMEDOUT) log_error("connect to %s got a timeout", site_string(to)); else log_error("connect to %s got an error: %s", site_string(to), strerror(errno)); goto error; } to->tcp_fd = s; found: return 1; error: if (s >= 0) close(s); return -1; } int booth_tcp_send(struct booth_site *to, void *buf, int len) { int rv; rv = add_hmac(buf, len); if (!rv) rv = do_write(to->tcp_fd, buf, len); return rv; } static int booth_tcp_recv(struct booth_site *from, void *buf, int len) { int got; /* Needs timeouts! */ got = do_read(from->tcp_fd, buf, len); if (got < 0) { log_error("read failed (%d): %s", errno, strerror(errno)); return got; } return got; } static int booth_tcp_recv_auth(struct booth_site *from, void *buf, int len) { int got, total; int payload_len; /* Needs timeouts! */ payload_len = len - sizeof(struct hmac); got = booth_tcp_recv(from, buf, payload_len); if (got < 0) { return got; } total = got; if (is_auth_req()) { got = booth_tcp_recv(from, (unsigned char *)buf+payload_len, sizeof(struct hmac)); if (got != sizeof(struct hmac) || check_auth(from, buf, len)) { return -1; } total += got; } return total; } static int booth_tcp_close(struct booth_site *to) { if (to) { if (to->tcp_fd > STDERR_FILENO) close(to->tcp_fd); to->tcp_fd = -1; } return 0; } static int booth_tcp_exit(void) { return 0; } static int setup_udp_server(void) { int rv, fd; int one = 1; unsigned int recvbuf_size; fd = socket(local->family, SOCK_DGRAM, 0); if (fd == -1) { log_error("failed to create UDP socket %s", strerror(errno)); goto ex; } rv = fcntl(fd, F_SETFL, O_NONBLOCK); if (rv == -1) { log_error("failed to set non-blocking operation " "on UDP socket: %s", strerror(errno)); goto ex; } rv = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)); if (rv == -1) { log_error("failed to set the SO_REUSEADDR option"); goto ex; } rv = bind(fd, (struct sockaddr *)&local->sa6, local->saddrlen); if (rv == -1) { log_error("failed to bind UDP socket to [%s]:%d: %s", site_string(local), booth_conf->port, strerror(errno)); goto ex; } recvbuf_size = SOCKET_BUFFER_SIZE; rv = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &recvbuf_size, sizeof(recvbuf_size)); if (rv == -1) { log_error("failed to set recvbuf size"); goto ex; } local->udp_fd = fd; return 0; ex: if (fd >= 0) close(fd); return -1; } /* Receive/process callback for UDP */ static void process_recv(int ci) { struct sockaddr_storage sa; int rv; socklen_t sa_len; /* beware, the buffer needs to be large enough to accept * a packet */ char buffer[MAX_MSG_LEN]; /* Used for unit tests */ struct boothc_ticket_msg *msg; sa_len = sizeof(sa); msg = (void*)buffer; rv = recvfrom(clients[ci].fd, buffer, sizeof(buffer), MSG_NOSIGNAL | MSG_DONTWAIT, (struct sockaddr *)&sa, &sa_len); if (rv == -1) return; rv = deliver_fn((void*)msg, rv); if (rv > 0) { if (getnameinfo((struct sockaddr *)&sa, sa_len, buffer, sizeof(buffer), NULL, 0, NI_NUMERICHOST) == 0) log_error("unknown sender: %08x (real: %s)", rv, buffer); else log_error("unknown sender: %08x", rv); } } static int booth_udp_init(void *f) { int rv; rv = setup_udp_server(); if (rv < 0) return rv; deliver_fn = f; client_add(local->udp_fd, booth_transport + UDP, process_recv, NULL); return 0; } int booth_udp_send(struct booth_site *to, void *buf, int len) { int rv; to->sent_cnt++; rv = sendto(local->udp_fd, buf, len, MSG_NOSIGNAL, (struct sockaddr *)&to->sa6, to->saddrlen); if (rv == len) { rv = 0; } else if (rv < 0) { to->sent_err_cnt++; log_error("Cannot send to %s: %d %s", site_string(to), errno, strerror(errno)); } else { rv = -1; to->sent_err_cnt++; log_error("Packet sent to %s got truncated", site_string(to)); } return rv; } int booth_udp_send_auth(struct booth_site *to, void *buf, int len) { int rv; rv = add_hmac(buf, len); if (rv < 0) return rv; return booth_udp_send(to, buf, len); } static int booth_udp_broadcast_auth(void *buf, int len) { int i, rv, rvs; struct booth_site *site; if (!booth_conf || !booth_conf->site_count) return -1; rv = add_hmac(buf, len); if (rv < 0) return rv; rvs = 0; foreach_node(i, site) { if (site != local) { rv = booth_udp_send(site, buf, len); if (!rvs) rvs = rv; } } return rvs; } static int booth_udp_exit(void) { return 0; } /* SCTP transport layer has not been developed yet */ static int booth_sctp_init(void *f __attribute__((unused))) { return 0; } static int booth_sctp_send(struct booth_site * to __attribute__((unused)), void *buf __attribute__((unused)), int len __attribute__((unused))) { return 0; } static int booth_sctp_broadcast(void *buf __attribute__((unused)), int len __attribute__((unused))) { return 0; } static int return_0_booth_site(struct booth_site *v __attribute((unused))) { return 0; } static int return_0(void) { return 0; } const struct booth_transport booth_transport[TRANSPORT_ENTRIES] = { [TCP] = { .name = "TCP", .init = booth_tcp_init, .open = booth_tcp_open, .send = booth_tcp_send, .recv = booth_tcp_recv, .recv_auth = booth_tcp_recv_auth, .close = booth_tcp_close, .exit = booth_tcp_exit }, [UDP] = { .name = "UDP", .init = booth_udp_init, .open = return_0_booth_site, .send = booth_udp_send, .send_auth = booth_udp_send_auth, .close = return_0_booth_site, .broadcast_auth = booth_udp_broadcast_auth, .exit = booth_udp_exit }, [SCTP] = { .name = "SCTP", .init = booth_sctp_init, .open = return_0_booth_site, .send = booth_sctp_send, .broadcast = booth_sctp_broadcast, .exit = return_0, } }; /* data + (datalen-sizeof(struct hmac)) points to struct hmac * i.e. struct hmac is always tacked on the payload */ int add_hmac(void *data, int len) { int rv = 0; -#if HAVE_LIBGCRYPT || HAVE_LIBMHASH +#if HAVE_LIBGNUTLS || HAVE_LIBGCRYPT || HAVE_LIBMHASH int payload_len; struct hmac *hp; if (!is_auth_req()) return 0; payload_len = len - sizeof(struct hmac); hp = (struct hmac *)((unsigned char *)data + payload_len); hp->hid = htonl(BOOTH_HASH); memset(hp->hash, 0, BOOTH_MAC_SIZE); rv = calc_hmac(data, payload_len, BOOTH_HASH, hp->hash, booth_conf->authkey, booth_conf->authkey_len); if (rv < 0) { log_error("internal error: cannot calculate mac"); } #endif return rv; } -#if HAVE_LIBGCRYPT || HAVE_LIBMHASH +#if HAVE_LIBGNUTLS || HAVE_LIBGCRYPT || HAVE_LIBMHASH /* TODO: we need some client identification for logging */ #define peer_string(p) (p ? site_string(p) : "client") /* verify the validity of timestamp from the header * the timestamp needs to be either greater than the one already * recorded for the site or, and this is checked for clients, * not to be older than booth_conf->maxtimeskew * update the timestamp for the site, if this packet is from a * site */ static int verify_ts(struct booth_site *from, void *buf, int len) { struct boothc_header *h; struct timeval tv, curr_tv, now; if (len < sizeof(*h)) { log_error("%s: packet too short", peer_string(from)); return -1; } h = (struct boothc_header *)buf; tv.tv_sec = ntohl(h->secs); tv.tv_usec = ntohl(h->usecs); if (from) { curr_tv.tv_sec = from->last_secs; curr_tv.tv_usec = from->last_usecs; if (timercmp(&tv, &curr_tv, >)) goto accept; log_warn("%s: packet timestamp older than previous one", site_string(from)); } gettimeofday(&now, NULL); now.tv_sec -= booth_conf->maxtimeskew; if (timercmp(&tv, &now, >)) goto accept; log_error("%s: packet timestamp older than %d seconds", peer_string(from), booth_conf->maxtimeskew); return -1; accept: if (from) { from->last_secs = tv.tv_sec; from->last_usecs = tv.tv_usec; } return 0; } #endif int check_auth(struct booth_site *from, void *buf, int len) { int rv = 0; -#if HAVE_LIBGCRYPT || HAVE_LIBMHASH +#if HAVE_LIBGNUTLS || HAVE_LIBGCRYPT || HAVE_LIBMHASH int payload_len; struct hmac *hp; if (!is_auth_req()) return 0; payload_len = len - sizeof(struct hmac); if (payload_len < 0) { log_error("%s: failed to authenticate, packet too short (size:%d)", peer_string(from), len); return -1; } hp = (struct hmac *)((unsigned char *)buf + payload_len); rv = verify_hmac(buf, payload_len, ntohl(hp->hid), hp->hash, booth_conf->authkey, booth_conf->authkey_len); if (!rv) { rv = verify_ts(from, buf, len); } if (rv != 0) { log_error("%s: failed to authenticate", peer_string(from)); } #endif return rv; } int send_data(int fd, void *data, int datalen) { int rv = 0; rv = add_hmac(data, datalen); if (!rv) rv = do_write(fd, data, datalen); return rv; } int send_header_plus(int fd, struct boothc_hdr_msg *msg, void *data, int len) { int rv; rv = send_data(fd, msg, sendmsglen(msg)-len); if (rv >= 0 && len) rv = do_write(fd, data, len); return rv; } /* UDP message receiver (see also deliver_fn declaration's comment) */ int message_recv(void *msg, int msglen) { uint32_t from; struct boothc_header *header; struct booth_site *source; header = (struct boothc_header *)msg; from = ntohl(header->from); if (!find_site_by_id(from, &source)) { /* caller knows the actual source address, pass the (assuredly) positive number and let it report */ from = from ? from : ~from; /* avoid 0 (success) */ return from & (~0U >> 1); /* avoid negative (error code} */ } time(&source->last_recv); source->recv_cnt++; if (check_boothc_header(header, msglen) < 0) { log_error("message from %s receive error", site_string(source)); source->recv_err_cnt++; return -1; } if (check_auth(source, msg, msglen)) { log_error("%s failed to authenticate", site_string(source)); source->sec_cnt++; return -1; } if (ntohl(header->opts) & BOOTH_OPT_ATTR) { /* not used, clients send/retrieve attributes directly * from sites */ return attr_recv(msg, source); } else { return ticket_recv(msg, source); } }