diff --git a/acinclude.m4 b/acinclude.m4 index cc5870ed8..98a87f673 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -1,186 +1,180 @@ dnl macros to configure gnupg dnl Copyright (C) 1998, 1999, 2000, 2001, 2003 Free Software Foundation, Inc. dnl dnl This file is part of GnuPG. dnl dnl GnuPG is free software; you can redistribute it and/or modify dnl it under the terms of the GNU General Public License as published by dnl the Free Software Foundation; either version 3 of the License, or dnl (at your option) any later version. dnl dnl GnuPG is distributed in the hope that it will be useful, dnl but WITHOUT ANY WARRANTY; without even the implied warranty of dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the dnl GNU General Public License for more details. dnl dnl You should have received a copy of the GNU General Public License dnl along with this program; if not, see . dnl GNUPG_CHECK_TYPEDEF(TYPE, HAVE_NAME) dnl Check whether a typedef exists and create a #define $2 if it exists dnl AC_DEFUN([GNUPG_CHECK_TYPEDEF], [ AC_MSG_CHECKING(for $1 typedef) AC_CACHE_VAL(gnupg_cv_typedef_$1, [AC_TRY_COMPILE([#define _GNU_SOURCE 1 #include #include ], [ #undef $1 int a = sizeof($1); ], gnupg_cv_typedef_$1=yes, gnupg_cv_typedef_$1=no )]) AC_MSG_RESULT($gnupg_cv_typedef_$1) if test "$gnupg_cv_typedef_$1" = yes; then AC_DEFINE($2,1,[Defined if a `]$1[' is typedef'd]) fi ]) dnl GNUPG_CHECK_GNUMAKE dnl AC_DEFUN([GNUPG_CHECK_GNUMAKE], [ if ${MAKE-make} --version 2>/dev/null | grep '^GNU ' >/dev/null 2>&1; then : else AC_MSG_WARN([[ *** *** It seems that you are not using GNU make. Some make tools have serious *** flaws and you may not be able to build this software at all. Before you *** complain, please try GNU make: GNU make is easy to build and available *** at all GNU archives. It is always available from ftp.gnu.org:/gnu/make. ***]]) fi ]) dnl GNUPG_CHECK_ENDIAN dnl define either LITTLE_ENDIAN_HOST or BIG_ENDIAN_HOST dnl AC_DEFUN([GNUPG_CHECK_ENDIAN], [ tmp_assumed_endian=big tmp_assume_warn="" if test "$cross_compiling" = yes; then case "$host_cpu" in i@<:@345678@:>@* ) tmp_assumed_endian=little ;; *) ;; esac fi AC_MSG_CHECKING(endianness) AC_CACHE_VAL(gnupg_cv_c_endian, [ gnupg_cv_c_endian=unknown # See if sys/param.h defines the BYTE_ORDER macro. AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include #include ]], [[ #if !BYTE_ORDER || !BIG_ENDIAN || !LITTLE_ENDIAN bogus endian macros #endif]])], [# It does; now see whether it defined to BIG_ENDIAN or not. AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include #include ]], [[ #if BYTE_ORDER != BIG_ENDIAN not big endian #endif]])], gnupg_cv_c_endian=big, gnupg_cv_c_endian=little)]) if test "$gnupg_cv_c_endian" = unknown; then AC_RUN_IFELSE([AC_LANG_SOURCE([[main () { /* Are we little or big endian? From Harbison&Steele. */ union { long l; char c[sizeof (long)]; } u; u.l = 1; exit (u.c[sizeof (long) - 1] == 1); }]])], gnupg_cv_c_endian=little, gnupg_cv_c_endian=big, gnupg_cv_c_endian=$tmp_assumed_endian tmp_assumed_warn=" (assumed)" ) fi ]) AC_MSG_RESULT([${gnupg_cv_c_endian}${tmp_assumed_warn}]) if test "$gnupg_cv_c_endian" = little; then AC_DEFINE(LITTLE_ENDIAN_HOST,1, [Defined if the host has little endian byte ordering]) else AC_DEFINE(BIG_ENDIAN_HOST,1, [Defined if the host has big endian byte ordering]) fi ]) # GNUPG_BUILD_PROGRAM(NAME,DEFAULT) # Add a --enable-NAME option to configure an set the # shell variable build_NAME either to "yes" or "no". DEFAULT must # either be "yes" or "no" and decided on the default value for # build_NAME and whether --enable-NAME or --disable-NAME is shown with # ./configure --help AC_DEFUN([GNUPG_BUILD_PROGRAM], [m4_define([my_build], [m4_bpatsubst(build_$1, [[^a-zA-Z0-9_]], [_])]) my_build=$2 m4_if([$2],[yes],[ AC_ARG_ENABLE([$1], AS_HELP_STRING([--disable-$1], [do not build the $1 program]), my_build=$enableval, my_build=$2) ],[ AC_ARG_ENABLE([$1], AS_HELP_STRING([--enable-$1], [build the $1 program]), my_build=$enableval, my_build=$2) ]) case "$my_build" in no|yes) ;; *) AC_MSG_ERROR([only yes or no allowed for feature --enable-$1]) ;; esac m4_undefine([my_build]) ]) # GNUPG_DISABLE_GPG_ALGO(NAME,DESCRIPTION) # # Add a --disable-gpg-NAME option and the corresponding ac_define # GPG_USE_. AC_DEFUN([GNUPG_GPG_DISABLE_ALGO], [AC_MSG_CHECKING([whether to enable the $2 for gpg]) AC_ARG_ENABLE([gpg-$1], AS_HELP_STRING([--disable-gpg-$1], [disable the $2 algorithm in gpg]), , enableval=yes) AC_MSG_RESULT($enableval) if test x"$enableval" = xyes ; then AC_DEFINE(GPG_USE_[]m4_toupper($1), 1, [Define to support the $2]) fi ]) # GNUPG_TIME_T_UNSIGNED # Check whether time_t is unsigned # AC_DEFUN([GNUPG_TIME_T_UNSIGNED], [ AC_CACHE_CHECK(whether time_t is unsigned, gnupg_cv_time_t_unsigned, - [AC_REQUIRE([AC_HEADER_TIME])dnl - AC_COMPILE_IFELSE([AC_LANG_BOOL_COMPILE_TRY( + [AC_COMPILE_IFELSE([AC_LANG_BOOL_COMPILE_TRY( [AC_INCLUDES_DEFAULT([]) -#if TIME_WITH_SYS_TIME +#if HAVE_SYS_TIME_H # include -# include #else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif +# include #endif ], [((time_t)-1) < 0])], gnupg_cv_time_t_unsigned=no, gnupg_cv_time_t_unsigned=yes)]) if test $gnupg_cv_time_t_unsigned = yes; then AC_DEFINE(HAVE_UNSIGNED_TIME_T,1,[Defined if time_t is an unsigned type]) fi ])# GNUPG_TIME_T_UNSIGNED diff --git a/configure.ac b/configure.ac index 69e9760b3..f61586712 100644 --- a/configure.ac +++ b/configure.ac @@ -1,2234 +1,2227 @@ # configure.ac - for GnuPG 2.1 # Copyright (C) 1998-2019 Werner Koch # Copyright (C) 1998-2021 Free Software Foundation, Inc. # Copyright (C) 2003-2021 g10 Code GmbH # # This file is part of GnuPG. # # GnuPG 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 3 of the License, or # (at your option) any later version. # # GnuPG 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, see . # Process this file with autoconf to produce a configure script. -AC_PREREQ([2.61]) +AC_PREREQ([2.69]) min_automake_version="1.16.3" # To build a release you need to create a tag with the version number # (git tag -s gnupg-2.n.m) and run "./autogen.sh --force". Please # bump the version number immediately *after* the release and do # another commit and push so that the git magic is able to work. m4_define([mym4_package],[gnupg]) m4_define([mym4_major], [2]) m4_define([mym4_minor], [3]) m4_define([mym4_micro], [5]) # To start a new development series, i.e a new major or minor number # you need to mark an arbitrary commit before the first beta release # with an annotated tag. For example the 2.1 branch starts off with # the tag "gnupg-2.1-base". This is used as the base for counting # beta numbers before the first release of a series. # Below is m4 magic to extract and compute the git revision number, # the decimalized short revision number, a beta version string and a # flag indicating a development version (mym4_isbeta). Note that the # m4 processing is done by autoconf and not during the configure run. m4_define([mym4_verslist], m4_split(m4_esyscmd([./autogen.sh --find-version] \ mym4_package mym4_major mym4_minor mym4_micro),[:])) m4_define([mym4_isbeta], m4_argn(2, mym4_verslist)) m4_define([mym4_version], m4_argn(4, mym4_verslist)) m4_define([mym4_revision], m4_argn(7, mym4_verslist)) m4_define([mym4_revision_dec], m4_argn(8, mym4_verslist)) m4_esyscmd([echo ]mym4_version[>VERSION]) AC_INIT([mym4_package],[mym4_version],[https://bugs.gnupg.org]) # When changing the SWDB tag please also adjust the hard coded tags in # build-aux/speedo.mk, build-aux/getswdb.sh, and Makefile.am AC_DEFINE_UNQUOTED(GNUPG_SWDB_TAG, "gnupg24", [swdb tag for this branch]) NEED_GPGRT_VERSION=1.41 NEED_LIBGCRYPT_API=1 NEED_LIBGCRYPT_VERSION=1.9.1 NEED_LIBASSUAN_API=2 NEED_LIBASSUAN_VERSION=2.5.0 NEED_KSBA_API=1 NEED_KSBA_VERSION=1.3.4 NEED_NTBTLS_API=1 NEED_NTBTLS_VERSION=0.1.0 NEED_NPTH_API=1 NEED_NPTH_VERSION=1.2 NEED_GNUTLS_VERSION=3.0 NEED_SQLITE_VERSION=3.27 development_version=mym4_isbeta PACKAGE=$PACKAGE_NAME PACKAGE_GT=${PACKAGE_NAME}2 VERSION=$PACKAGE_VERSION AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_SRCDIR([sm/gpgsm.c]) -AC_CONFIG_HEADER([config.h]) +AC_CONFIG_HEADERS([config.h]) AM_INIT_AUTOMAKE([serial-tests dist-bzip2 no-dist-gzip]) AC_CANONICAL_HOST AB_INIT AC_USE_SYSTEM_EXTENSIONS # Some status variables. have_gpg_error=no have_libgcrypt=no have_libassuan=no have_ksba=no have_ntbtls=no have_gnutls=no have_sqlite=no have_npth=no have_libusb=no have_libtss=no have_system_resolver=no gnupg_have_ldap="n/a" use_zip=yes use_bzip2=yes use_exec=yes use_trust_models=yes use_tofu=yes use_libdns=yes card_support=yes use_ccid_driver=auto dirmngr_auto_start=yes use_tls_library=no large_secmem=no show_tor_support=no # gpg is a required part and can't be disabled anymore. build_gpg=yes GNUPG_BUILD_PROGRAM(gpgsm, yes) # The agent is a required part and can't be disabled anymore. build_agent=yes GNUPG_BUILD_PROGRAM(scdaemon, yes) GNUPG_BUILD_PROGRAM(g13, no) GNUPG_BUILD_PROGRAM(dirmngr, yes) GNUPG_BUILD_PROGRAM(keyboxd, yes) GNUPG_BUILD_PROGRAM(tpm2d, yes) GNUPG_BUILD_PROGRAM(doc, yes) # We use gpgtar to unpack test data, hence we always build it. If the # user opts out, we simply don't install it. GNUPG_BUILD_PROGRAM(gpgtar, yes) # We also install the gpg-wks-server tool by default but disable it # later for platforms where it can't be build. GNUPG_BUILD_PROGRAM(wks-tools, yes) AC_SUBST(PACKAGE) AC_SUBST(PACKAGE_GT) AC_SUBST(VERSION) AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of this package]) AC_DEFINE_UNQUOTED(PACKAGE_GT, "$PACKAGE_GT", [Name of this package for gettext]) AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version of this package]) AC_DEFINE_UNQUOTED(PACKAGE_BUGREPORT, "$PACKAGE_BUGREPORT", [Bug report address]) AC_DEFINE_UNQUOTED(NEED_LIBGCRYPT_VERSION, "$NEED_LIBGCRYPT_VERSION", [Required version of Libgcrypt]) AC_DEFINE_UNQUOTED(NEED_KSBA_VERSION, "$NEED_KSBA_VERSION", [Required version of Libksba]) AC_DEFINE_UNQUOTED(NEED_NTBTLS_VERSION, "$NEED_NTBTLS_VERSION", [Required version of NTBTLS]) # The default is to use the modules from this package and the few # other packages in a standard place; i.e where this package gets # installed. With these options it is possible to override these # ${prefix} depended values with fixed paths, which can't be replaced # at make time. See also am/cmacros.am and the defaults in AH_BOTTOM. AC_ARG_WITH(agent-pgm, [ --with-agent-pgm=PATH Use PATH as the default for the agent)], GNUPG_AGENT_PGM="$withval", GNUPG_AGENT_PGM="" ) AC_SUBST(GNUPG_AGENT_PGM) AM_CONDITIONAL(GNUPG_AGENT_PGM, test -n "$GNUPG_AGENT_PGM") show_gnupg_agent_pgm="(default)" test -n "$GNUPG_AGENT_PGM" && show_gnupg_agent_pgm="$GNUPG_AGENT_PGM" AC_ARG_WITH(pinentry-pgm, [ --with-pinentry-pgm=PATH Use PATH as the default for the pinentry)], GNUPG_PINENTRY_PGM="$withval", GNUPG_PINENTRY_PGM="" ) AC_SUBST(GNUPG_PINENTRY_PGM) AM_CONDITIONAL(GNUPG_PINENTRY_PGM, test -n "$GNUPG_PINENTRY_PGM") show_gnupg_pinentry_pgm="(default)" test -n "$GNUPG_PINENTRY_PGM" && show_gnupg_pinentry_pgm="$GNUPG_PINENTRY_PGM" AC_ARG_WITH(scdaemon-pgm, [ --with-scdaemon-pgm=PATH Use PATH as the default for the scdaemon)], GNUPG_SCDAEMON_PGM="$withval", GNUPG_SCDAEMON_PGM="" ) AC_SUBST(GNUPG_SCDAEMON_PGM) AM_CONDITIONAL(GNUPG_SCDAEMON_PGM, test -n "$GNUPG_SCDAEMON_PGM") show_gnupg_scdaemon_pgm="(default)" test -n "$GNUPG_SCDAEMON_PGM" && show_gnupg_scdaemon_pgm="$GNUPG_SCDAEMON_PGM" AC_ARG_WITH(tpm2daemon-pgm, [ --with-tpm2daemon-pgm=PATH Use PATH as the default for the tpm2daemon)], GNUPG_TPM2DAEMON_PGM="$withval", GNUPG_TPM2DAEMON_PGM="" ) AC_SUBST(GNUPG_TPM2DAEMON_PGM) AM_CONDITIONAL(GNUPG_TPM2DAEMON_PGM, test -n "$GNUPG_TPM2DAEMON_PGM") show_gnupg_tpm2daemon_pgm="(default)" test -n "$GNUPG_TPM2DAEMON_PGM" && show_gnupg_tpm2daemon_pgm="$GNUPG_TPM2DAEMON_PGM" AC_ARG_WITH(dirmngr-pgm, [ --with-dirmngr-pgm=PATH Use PATH as the default for the dirmngr)], GNUPG_DIRMNGR_PGM="$withval", GNUPG_DIRMNGR_PGM="" ) AC_SUBST(GNUPG_DIRMNGR_PGM) AM_CONDITIONAL(GNUPG_DIRMNGR_PGM, test -n "$GNUPG_DIRMNGR_PGM") show_gnupg_dirmngr_pgm="(default)" test -n "$GNUPG_DIRMNGR_PGM" && show_gnupg_dirmngr_pgm="$GNUPG_DIRMNGR_PGM" AC_ARG_WITH(keyboxd-pgm, [ --with-keyboxd-pgm=PATH Use PATH as the default for the keyboxd)], GNUPG_KEYBOXD_PGM="$withval", GNUPG_KEYBOXD_PGM="" ) AC_SUBST(GNUPG_KEYBOXD_PGM) AM_CONDITIONAL(GNUPG_KEYBOXD_PGM, test -n "$GNUPG_KEYBOXD_PGM") show_gnupg_keyboxd_pgm="(default)" test -n "$GNUPG_KEYBOXD_PGM" && show_gnupg_keyboxd_pgm="$GNUPG_KEYBOXD_PGM" AC_ARG_WITH(protect-tool-pgm, [ --with-protect-tool-pgm=PATH Use PATH as the default for the protect-tool)], GNUPG_PROTECT_TOOL_PGM="$withval", GNUPG_PROTECT_TOOL_PGM="" ) AC_SUBST(GNUPG_PROTECT_TOOL_PGM) AM_CONDITIONAL(GNUPG_PROTECT_TOOL_PGM, test -n "$GNUPG_PROTECT_TOOL_PGM") show_gnupg_protect_tool_pgm="(default)" test -n "$GNUPG_PROTECT_TOOL_PGM" \ && show_gnupg_protect_tool_pgm="$GNUPG_PROTECT_TOOL_PGM" AC_ARG_WITH(dirmngr-ldap-pgm, [ --with-dirmngr-ldap-pgm=PATH Use PATH as the default for the dirmngr ldap wrapper)], GNUPG_DIRMNGR_LDAP_PGM="$withval", GNUPG_DIRMNGR_LDAP_PGM="" ) AC_SUBST(GNUPG_DIRMNGR_LDAP_PGM) AM_CONDITIONAL(GNUPG_DIRMNGR_LDAP_PGM, test -n "$GNUPG_DIRMNGR_LDAP_PGM") show_gnupg_dirmngr_ldap_pgm="(default)" test -n "$GNUPG_DIRMNGR_LDAP_PGM" \ && show_gnupg_dirmngr_ldap_pgm="$GNUPG_DIRMNGR_LDAP_PGM" # # For a long time gpg 2.x was installed as gpg2. This changed with # 2.2. This option can be used to install gpg under the name gpg2. # AC_ARG_ENABLE(gpg-is-gpg2, AS_HELP_STRING([--enable-gpg-is-gpg2],[Set installed name of gpg to gpg2]), gpg_is_gpg2=$enableval) if test "$gpg_is_gpg2" = "yes"; then AC_DEFINE(USE_GPG2_HACK, 1, [Define to install gpg as gpg2]) fi AM_CONDITIONAL(USE_GPG2_HACK, test "$gpg_is_gpg2" = "yes") # SELinux support includes tracking of sensitive files to avoid # leaking their contents through processing these files by gpg itself AC_MSG_CHECKING([whether SELinux support is requested]) AC_ARG_ENABLE(selinux-support, AS_HELP_STRING([--enable-selinux-support], [enable SELinux support]), selinux_support=$enableval, selinux_support=no) AC_MSG_RESULT($selinux_support) AC_MSG_CHECKING([whether to allocate extra secure memory]) AC_ARG_ENABLE(large-secmem, AS_HELP_STRING([--enable-large-secmem], [allocate extra secure memory]), large_secmem=$enableval, large_secmem=no) AC_MSG_RESULT($large_secmem) if test "$large_secmem" = yes ; then SECMEM_BUFFER_SIZE=65536 else SECMEM_BUFFER_SIZE=32768 fi AC_DEFINE_UNQUOTED(SECMEM_BUFFER_SIZE,$SECMEM_BUFFER_SIZE, [Size of secure memory buffer]) AC_MSG_CHECKING([calibrated passphrase-stretching (s2k) duration]) AC_ARG_WITH(agent-s2k-calibration, AS_HELP_STRING([--with-agent-s2k-calibration=MSEC], [calibrate passphrase stretching (s2k) to MSEC milliseconds]), agent_s2k_calibration=$withval, agent_s2k_calibration=100) AC_MSG_RESULT($agent_s2k_calibration milliseconds) AC_DEFINE_UNQUOTED(AGENT_S2K_CALIBRATION, $agent_s2k_calibration, [Agent s2k calibration time (ms)]) AC_MSG_CHECKING([whether to enable trust models]) AC_ARG_ENABLE(trust-models, AS_HELP_STRING([--disable-trust-models], [disable all trust models except "always"]), use_trust_models=$enableval) AC_MSG_RESULT($use_trust_models) if test "$use_trust_models" = no ; then AC_DEFINE(NO_TRUST_MODELS, 1, [Define to include only trust-model always]) fi AC_MSG_CHECKING([whether to enable TOFU]) AC_ARG_ENABLE(tofu, AS_HELP_STRING([--disable-tofu], [disable the TOFU trust model]), use_tofu=$enableval, use_tofu=$use_trust_models) AC_MSG_RESULT($use_tofu) if test "$use_trust_models" = no && test "$use_tofu" = yes; then AC_MSG_ERROR([both --disable-trust-models and --enable-tofu given]) fi AC_MSG_CHECKING([whether to enable libdns]) AC_ARG_ENABLE(libdns, AS_HELP_STRING([--disable-libdns], [do not build with libdns support]), use_libdns=$enableval, use_libdns=yes) AC_MSG_RESULT($use_libdns) if test x"$use_libdns" = xyes ; then AC_DEFINE(USE_LIBDNS, 1, [Build with integrated libdns support]) fi AM_CONDITIONAL(USE_LIBDNS, test "$use_libdns" = yes) # # Options to disable algorithm # GNUPG_GPG_DISABLE_ALGO([rsa],[RSA public key]) # Elgamal is a MUST algorithm # DSA is a MUST algorithm GNUPG_GPG_DISABLE_ALGO([ecdh],[ECDH public key]) GNUPG_GPG_DISABLE_ALGO([ecdsa],[ECDSA public key]) GNUPG_GPG_DISABLE_ALGO([eddsa],[EdDSA public key]) GNUPG_GPG_DISABLE_ALGO([idea],[IDEA cipher]) # 3DES is a MUST algorithm GNUPG_GPG_DISABLE_ALGO([cast5],[CAST5 cipher]) GNUPG_GPG_DISABLE_ALGO([blowfish],[BLOWFISH cipher]) GNUPG_GPG_DISABLE_ALGO([aes128],[AES128 cipher]) GNUPG_GPG_DISABLE_ALGO([aes192],[AES192 cipher]) GNUPG_GPG_DISABLE_ALGO([aes256],[AES256 cipher]) GNUPG_GPG_DISABLE_ALGO([twofish],[TWOFISH cipher]) GNUPG_GPG_DISABLE_ALGO([camellia128],[CAMELLIA128 cipher]) GNUPG_GPG_DISABLE_ALGO([camellia192],[CAMELLIA192 cipher]) GNUPG_GPG_DISABLE_ALGO([camellia256],[CAMELLIA256 cipher]) GNUPG_GPG_DISABLE_ALGO([md5],[MD5 hash]) # SHA1 is a MUST algorithm GNUPG_GPG_DISABLE_ALGO([rmd160],[RIPE-MD160 hash]) GNUPG_GPG_DISABLE_ALGO([sha224],[SHA-224 hash]) # SHA256 is a MUST algorithm for GnuPG. GNUPG_GPG_DISABLE_ALGO([sha384],[SHA-384 hash]) GNUPG_GPG_DISABLE_ALGO([sha512],[SHA-512 hash]) # Allow disabling of zip support. # This is in general not a good idea because according to rfc4880 OpenPGP # implementations SHOULD support ZLIB. AC_MSG_CHECKING([whether to enable the ZIP and ZLIB compression algorithm]) AC_ARG_ENABLE(zip, AS_HELP_STRING([--disable-zip], [disable the ZIP and ZLIB compression algorithm]), use_zip=$enableval) AC_MSG_RESULT($use_zip) # Allow disabling of bzib2 support. # It is defined only after we confirm the library is available later AC_MSG_CHECKING([whether to enable the BZIP2 compression algorithm]) AC_ARG_ENABLE(bzip2, AS_HELP_STRING([--disable-bzip2],[disable the BZIP2 compression algorithm]), use_bzip2=$enableval) AC_MSG_RESULT($use_bzip2) # Configure option to allow or disallow execution of external # programs, like a photo viewer. AC_MSG_CHECKING([whether to enable external program execution]) AC_ARG_ENABLE(exec, AS_HELP_STRING([--disable-exec],[disable all external program execution]), use_exec=$enableval) AC_MSG_RESULT($use_exec) if test "$use_exec" = no ; then AC_DEFINE(NO_EXEC,1,[Define to disable all external program execution]) fi if test "$use_exec" = yes ; then AC_MSG_CHECKING([whether to enable photo ID viewing]) AC_ARG_ENABLE(photo-viewers, [ --disable-photo-viewers disable photo ID viewers], [if test "$enableval" = no ; then AC_DEFINE(DISABLE_PHOTO_VIEWER,1,[define to disable photo viewing]) fi],enableval=yes) gnupg_cv_enable_photo_viewers=$enableval AC_MSG_RESULT($enableval) if test "$gnupg_cv_enable_photo_viewers" = yes ; then AC_MSG_CHECKING([whether to use a fixed photo ID viewer]) AC_ARG_WITH(photo-viewer, [ --with-photo-viewer=FIXED_VIEWER set a fixed photo ID viewer], [if test "$withval" = yes ; then withval=no elif test "$withval" != no ; then AC_DEFINE_UNQUOTED(FIXED_PHOTO_VIEWER,"$withval", [if set, restrict photo-viewer to this]) fi],withval=no) AC_MSG_RESULT($withval) fi fi # # Check for the key/uid cache size. This can't be zero, but can be # pretty small on embedded systems. This is used for the gpg part. # AC_MSG_CHECKING([for the size of the key and uid cache]) AC_ARG_ENABLE(key-cache, AS_HELP_STRING([--enable-key-cache=SIZE], [Set key cache to SIZE (default 4096)]),,enableval=4096) if test "$enableval" = "no"; then enableval=5 elif test "$enableval" = "yes" || test "$enableval" = ""; then enableval=4096 fi changequote(,)dnl key_cache_size=`echo "$enableval" | sed 's/[A-Za-z]//g'` changequote([,])dnl if test "$enableval" != "$key_cache_size" || test "$key_cache_size" -lt 5; then AC_MSG_ERROR([invalid key-cache size]) fi AC_MSG_RESULT($key_cache_size) AC_DEFINE_UNQUOTED(PK_UID_CACHE_SIZE,$key_cache_size, [Size of the key and UID caches]) # # Check whether we want to use Linux capabilities # AC_MSG_CHECKING([whether use of capabilities is requested]) AC_ARG_WITH(capabilities, [ --with-capabilities use linux capabilities [default=no]], [use_capabilities="$withval"],[use_capabilities=no]) AC_MSG_RESULT($use_capabilities) # # Check whether to disable the card support AC_MSG_CHECKING([whether smartcard support is requested]) AC_ARG_ENABLE(card-support, AS_HELP_STRING([--disable-card-support], [disable smartcard support]), card_support=$enableval) AC_MSG_RESULT($card_support) if test "$card_support" = yes ; then AC_DEFINE(ENABLE_CARD_SUPPORT,1,[Define to include smartcard support]) else build_scdaemon=no fi # # Allow disabling of internal CCID support. # It is defined only after we confirm the library is available later # AC_MSG_CHECKING([whether to enable the internal CCID driver]) AC_ARG_ENABLE(ccid-driver, AS_HELP_STRING([--disable-ccid-driver], [disable the internal CCID driver]), use_ccid_driver=$enableval) AC_MSG_RESULT($use_ccid_driver) AC_MSG_CHECKING([whether to auto start dirmngr]) AC_ARG_ENABLE(dirmngr-auto-start, AS_HELP_STRING([--disable-dirmngr-auto-start], [disable auto starting of the dirmngr]), dirmngr_auto_start=$enableval) AC_MSG_RESULT($dirmngr_auto_start) if test "$dirmngr_auto_start" = yes ; then AC_DEFINE(USE_DIRMNGR_AUTO_START,1, [Define to enable auto starting of the dirmngr]) fi # # To avoid double inclusion of config.h which might happen at some # places, we add the usual double inclusion protection at the top of # config.h. # AH_TOP([ #ifndef GNUPG_CONFIG_H_INCLUDED #define GNUPG_CONFIG_H_INCLUDED ]) # # Stuff which goes at the bottom of config.h. # AH_BOTTOM([ /* This is the major version number of GnuPG so that source included files can test for this. Note, that we use 2 here even for GnuPG 1.9.x. */ #define GNUPG_MAJOR_VERSION 2 /* Now to separate file name parts. Please note that the string version must not contain more than one character because the code assumes strlen()==1 */ #ifdef HAVE_DOSISH_SYSTEM #define DIRSEP_C '\\' #define DIRSEP_S "\\" #define EXTSEP_C '.' #define EXTSEP_S "." #define PATHSEP_C ';' #define PATHSEP_S ";" #define EXEEXT_S ".exe" #else #define DIRSEP_C '/' #define DIRSEP_S "/" #define EXTSEP_C '.' #define EXTSEP_S "." #define PATHSEP_C ':' #define PATHSEP_S ":" #define EXEEXT_S "" #endif /* Some global constants. * Note that the homedir must not end in a slash. */ #ifdef HAVE_DOSISH_SYSTEM # ifdef HAVE_DRIVE_LETTERS # define GNUPG_DEFAULT_HOMEDIR "c:/gnupg" # else # define GNUPG_DEFAULT_HOMEDIR "/gnupg" # endif #elif defined(__VMS) #define GNUPG_DEFAULT_HOMEDIR "/SYS$LOGIN/gnupg" #else #define GNUPG_DEFAULT_HOMEDIR "~/.gnupg" #endif #define GNUPG_PRIVATE_KEYS_DIR "private-keys-v1.d" #define GNUPG_PUBLIC_KEYS_DIR "public-keys.d" #define GNUPG_OPENPGP_REVOC_DIR "openpgp-revocs.d" #define GNUPG_CACHE_DIR "cache.d" /* GnuPG has always been a part of the GNU project and thus we have * shown the FSF as holder of the copyright. We continue to do so for * the reason that without the FSF the free software used all over the * world would not have come into existence. However, under Windows * we print a different copyright string with --version because the * copyright assignments of g10 Code and Werner Koch were terminated * many years ago, g10 Code is still the major contributor to the * code, and Windows is not an FSF endorsed platform. Note that the * actual list of copyright holders can be found in the AUTHORS file. */ #ifdef HAVE_W32_SYSTEM #define GNUPG_DEF_COPYRIGHT_LINE "Copyright (C) 2021 g10 Code GmbH" #else #define GNUPG_DEF_COPYRIGHT_LINE \ "Copyright (C) 2021 Free Software Foundation, Inc." #endif /* For some systems (DOS currently), we hardcode the path here. For POSIX systems the values are constructed by the Makefiles, so that the values may be overridden by the make invocations; this is to comply with the GNU coding standards. Note that these values are only defaults. */ #ifdef HAVE_DOSISH_SYSTEM # ifdef HAVE_DRIVE_LETTERS # define GNUPG_BINDIR "c:\\gnupg" # define GNUPG_LIBEXECDIR "c:\\gnupg" # define GNUPG_LIBDIR "c:\\gnupg" # define GNUPG_DATADIR "c:\\gnupg" # define GNUPG_SYSCONFDIR "c:\\gnupg" # else # define GNUPG_BINDIR "\\gnupg" # define GNUPG_LIBEXECDIR "\\gnupg" # define GNUPG_LIBDIR "\\gnupg" # define GNUPG_DATADIR "\\gnupg" # define GNUPG_SYSCONFDIR "\\gnupg" # endif #endif /* Derive some other constants. */ #if !(defined(HAVE_FORK) && defined(HAVE_PIPE) && defined(HAVE_WAITPID)) #define EXEC_TEMPFILE_ONLY #endif /* We didn't define endianness above, so get it from OS macros. This is intended for making fat binary builds on OS X. */ #if !defined(BIG_ENDIAN_HOST) && !defined(LITTLE_ENDIAN_HOST) #if defined(__BIG_ENDIAN__) #define BIG_ENDIAN_HOST 1 #elif defined(__LITTLE_ENDIAN__) #define LITTLE_ENDIAN_HOST 1 #else #error "No endianness found" #endif #endif /* Hack used for W32: ldap.m4 also tests for the ASCII version of ldap_start_tls_s because that is the actual symbol used in the library. winldap.h redefines it to our commonly used value, thus we define our usual macro here. */ #ifdef HAVE_LDAP_START_TLS_SA # ifndef HAVE_LDAP_START_TLS_S # define HAVE_LDAP_START_TLS_S 1 # endif #endif /* Enable the es_ macros from gpgrt. */ #define GPGRT_ENABLE_ES_MACROS 1 /* Enable the log_ macros from gpgrt. */ #define GPGRT_ENABLE_LOG_MACROS 1 /* We want the argparse macros from gpgrt. */ #define GPGRT_ENABLE_ARGPARSE_MACROS 1 /* Tell libgcrypt not to use its own libgpg-error implementation. */ #define USE_LIBGPG_ERROR 1 /* Tell Libgcrypt not to include deprecated definitions. */ #define GCRYPT_NO_DEPRECATED 1 /* Our HTTP code is used in estream mode. */ #define HTTP_USE_ESTREAM 1 /* Under W32 we do an explicit socket initialization, thus we need to avoid the on-demand initialization which would also install an atexit handler. */ #define HTTP_NO_WSASTARTUP /* Under Windows we use the gettext code from libgpg-error. */ #define GPG_ERR_ENABLE_GETTEXT_MACROS /* Under WindowsCE we use the strerror replacement from libgpg-error. */ #define GPG_ERR_ENABLE_ERRNO_MACROS #endif /*GNUPG_CONFIG_H_INCLUDED*/ ]) AM_MAINTAINER_MODE AC_ARG_VAR(SYSROOT,[locate config scripts also below that directory]) # Checks for programs. AC_MSG_NOTICE([checking for programs]) AC_PROG_MAKE_SET AM_SANITY_CHECK missing_dir=`cd $ac_aux_dir && pwd` AM_MISSING_PROG(ACLOCAL, aclocal, $missing_dir) AM_MISSING_PROG(AUTOCONF, autoconf, $missing_dir) AM_MISSING_PROG(AUTOMAKE, automake, $missing_dir) AM_MISSING_PROG(AUTOHEADER, autoheader, $missing_dir) AM_MISSING_PROG(MAKEINFO, makeinfo, $missing_dir) AM_SILENT_RULES AC_PROG_AWK AC_PROG_CC AC_PROG_CPP AM_PROG_CC_C_O if test "x$ac_cv_prog_cc_c89" = "xno" ; then AC_MSG_ERROR([[No C-89 compiler found]]) fi AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_RANLIB AC_CHECK_TOOL(AR, ar, :) AC_PATH_PROG(PERL,"perl") AC_CHECK_TOOL(WINDRES, windres, :) AC_PATH_PROG(YAT2M, "yat2m", "./yat2m" ) AC_ARG_VAR(YAT2M, [tool to convert texi to man pages]) AM_CONDITIONAL(HAVE_YAT2M, test -n "$ac_cv_path_YAT2M") AC_SEARCH_LIBS([strerror],[cposix]) AC_SYS_LARGEFILE # GNU AWK requires -n option to interpret "0xHH" as a number if $AWK 'BEGIN { if (PROCINFO@<:@"version"@:>@) exit 1 }'; then AWK_HEX_NUMBER_OPTION='' AC_MSG_NOTICE([awk with no option for hexadecimal]) else AWK_HEX_NUMBER_OPTION='-n' AC_MSG_NOTICE([awk with an option -n for hexadecimal]) fi AC_SUBST(AWK_HEX_NUMBER_OPTION) # We need to compile and run a program on the build machine. A # comment in libgpg-error says that the AC_PROG_CC_FOR_BUILD macro in # the AC archive is broken for autoconf 2.57. Given that there is no # newer version of that macro, we assume that it is also broken for # autoconf 2.61 and thus we use a simple but usually sufficient # approach. AC_MSG_CHECKING(for cc for build) if test "$cross_compiling" = "yes"; then CC_FOR_BUILD="${CC_FOR_BUILD-cc}" else CC_FOR_BUILD="${CC_FOR_BUILD-$CC}" fi AC_MSG_RESULT($CC_FOR_BUILD) AC_ARG_VAR(CC_FOR_BUILD,[build system C compiler]) # We need to call this macro because other pkg-config macros are # not always used. PKG_PROG_PKG_CONFIG try_gettext=yes require_iconv=yes have_dosish_system=no have_w32_system=no have_w32ce_system=no have_android_system=no use_simple_gettext=no mmap_needed=yes require_pipe_to_unblock_pselect=yes case "${host}" in *-mingw32*) # special stuff for Windoze NT ac_cv_have_dev_random=no AC_DEFINE(USE_ONLY_8DOT3,1, [Set this to limit filenames to the 8.3 format]) AC_DEFINE(USE_SIMPLE_GETTEXT,1, [Because the Unix gettext has too much overhead on MingW32 systems and these systems lack Posix functions, we use a simplified version of gettext]) have_dosish_system=yes have_w32_system=yes require_iconv=no require_pipe_to_unblock_pselect=no case "${host}" in *-mingw32ce*) have_w32ce_system=yes ;; *) AC_DEFINE(HAVE_DRIVE_LETTERS,1, [Defined if the OS supports drive letters.]) ;; esac try_gettext="no" use_simple_gettext=yes mmap_needed=no build_wks_tools=no ;; i?86-emx-os2 | i?86-*-os2*emx ) # OS/2 with the EMX environment ac_cv_have_dev_random=no AC_DEFINE(HAVE_DRIVE_LETTERS) have_dosish_system=yes try_gettext="no" build_wks_tools=no ;; i?86-*-msdosdjgpp*) # DOS with the DJGPP environment ac_cv_have_dev_random=no AC_DEFINE(HAVE_DRIVE_LETTERS) have_dosish_system=yes try_gettext="no" build_wks_tools=no ;; *-*-hpux*) if test -z "$GCC" ; then CFLAGS="-Ae -D_HPUX_SOURCE $CFLAGS" fi ;; *-dec-osf4*) if test -z "$GCC" ; then # Suppress all warnings # to get rid of the unsigned/signed char mismatch warnings. CFLAGS="-w $CFLAGS" fi ;; *-dec-osf5*) if test -z "$GCC" ; then # Use the newer compiler `-msg_disable ptrmismatch1' to # get rid of the unsigned/signed char mismatch warnings. # Using this may hide other pointer mismatch warnings, but # it at least lets other warning classes through CFLAGS="-msg_disable ptrmismatch1 $CFLAGS" fi ;; m68k-atari-mint) ;; *-linux-android*) have_android_system=yes # Android is fully utf-8 and we do not want to use iconv to # keeps things simple require_iconv=no build_wks_tools=no ;; *-apple-darwin*) AC_DEFINE(_DARWIN_C_SOURCE, 1, Expose all libc features (__DARWIN_C_FULL).) ;; *-*-netbsd*) require_pipe_to_unblock_pselect=yes ;; *) ;; esac if test "$require_pipe_to_unblock_pselect" = yes; then AC_DEFINE(HAVE_PSELECT_NO_EINTR, 1, [Defined if we run on systems like NetBSD, where pselect cannot be unblocked by signal from a thread within the same process. We use pipe in this case, instead.]) fi if test "$have_dosish_system" = yes; then AC_DEFINE(HAVE_DOSISH_SYSTEM,1, [Defined if we run on some of the PCDOS like systems (DOS, Windoze. OS/2) with special properties like no file modes, case insensitive file names and preferred use of backslashes as directory name separators.]) fi AM_CONDITIONAL(HAVE_DOSISH_SYSTEM, test "$have_dosish_system" = yes) AM_CONDITIONAL(USE_SIMPLE_GETTEXT, test x"$use_simple_gettext" = xyes) if test "$have_w32_system" = yes; then AC_DEFINE(HAVE_W32_SYSTEM,1, [Defined if we run on a W32 API based system]) if test "$have_w32ce_system" = yes; then AC_DEFINE(HAVE_W32CE_SYSTEM,1,[Defined if we run on WindowsCE]) fi AC_CHECK_HEADERS([winsock2.h]) fi AM_CONDITIONAL(HAVE_W32_SYSTEM, test "$have_w32_system" = yes) AM_CONDITIONAL(HAVE_W32CE_SYSTEM, test "$have_w32ce_system" = yes) if test "$have_android_system" = yes; then AC_DEFINE(HAVE_ANDROID_SYSTEM,1, [Defined if we build for an Android system]) fi AM_CONDITIONAL(HAVE_ANDROID_SYSTEM, test "$have_android_system" = yes) # (These need to go after AC_PROG_CC so that $EXEEXT is defined) AC_DEFINE_UNQUOTED(EXEEXT,"$EXEEXT",[The executable file extension, if any]) # # Checks for libraries. # AC_MSG_NOTICE([checking for libraries]) # # gpgrt (aka libgpg-error) is a library with error codes shared # between GnuPG related projects. # AM_PATH_GPG_ERROR("$NEED_GPGRT_VERSION", have_gpg_error=yes,have_gpg_error=no) # # Libgcrypt is our generic crypto library # AM_PATH_LIBGCRYPT("$NEED_LIBGCRYPT_API:$NEED_LIBGCRYPT_VERSION", have_libgcrypt=yes,have_libgcrypt=no) # And, then, check if it's newer than 1.9.0 so that we can # conditionally build some programs. # Note: This is not anymore needed but keep the code commented in case # we need it again with some future libgcrypt. #have_libgcrypt_newer=no #if test $ok = yes; then # if test "$major" -gt 1; then # have_libgcrypt_newer=yes # else # if test "$major" -eq 1; then # if test "$minor" -gt 9; then # have_libgcrypt_newer=yes # else # if test "$minor" -eq 9; then # if test "$micro" -ge 0; then # have_libgcrypt_newer=yes # fi # fi # fi # fi # fiy #fi #AM_CONDITIONAL(HAVE_NEWER_LIBGCRYPT, [test $have_libgcrypt_newer = yes]) # # libassuan is used for IPC # AM_PATH_LIBASSUAN("$NEED_LIBASSUAN_API:$NEED_LIBASSUAN_VERSION", have_libassuan=yes,have_libassuan=no) if test "$have_libassuan" = "yes"; then AC_DEFINE_UNQUOTED(GNUPG_LIBASSUAN_VERSION, "$libassuan_version", [version of the libassuan library]) show_tor_support="only .onion" fi # # libksba is our X.509 support library # AM_PATH_KSBA("$NEED_KSBA_API:$NEED_KSBA_VERSION",have_ksba=yes,have_ksba=no) # # libusb allows us to use the integrated CCID smartcard reader driver. # # FiXME: Use GNUPG_CHECK_LIBUSB and modify to use separate AC_SUBSTs. if test "$use_ccid_driver" = auto || test "$use_ccid_driver" = yes; then case "${host}" in *-mingw32*) LIBUSB_NAME= LIBUSB_LIBS= LIBUSB_CPPFLAGS= ;; *-*-darwin*) LIBUSB_NAME=usb-1.0 LIBUSB_LIBS="-Wl,-framework,CoreFoundation -Wl,-framework,IOKit" ;; *-*-freebsd*) # FreeBSD has a native 1.0 compatible library by -lusb. LIBUSB_NAME=usb LIBUSB_LIBS= ;; *) LIBUSB_NAME=usb-1.0 LIBUSB_LIBS= ;; esac fi if test x"$LIBUSB_NAME" != x ; then AC_CHECK_LIB($LIBUSB_NAME, libusb_init, [ LIBUSB_LIBS="-l$LIBUSB_NAME $LIBUSB_LIBS" have_libusb=yes ]) AC_MSG_CHECKING([libusb include dir]) usb_incdir_found="no" for _incdir in "" "/usr/include/libusb-1.0" \ "/usr/local/include/libusb-1.0" "/usr/pkg/include/libusb-1.0"; do _libusb_save_cppflags=$CPPFLAGS if test -n "${_incdir}"; then CPPFLAGS="-I${_incdir} ${CPPFLAGS}" fi AC_PREPROC_IFELSE([AC_LANG_SOURCE([[@%:@include ]])], [usb_incdir=${_incdir}; usb_incdir_found="yes"], []) CPPFLAGS=${_libusb_save_cppflags} if test "$usb_incdir_found" = "yes"; then break fi done if test "$usb_incdir_found" = "yes"; then AC_MSG_RESULT([${usb_incdir}]) else AC_MSG_RESULT([not found]) usb_incdir="" have_libusb=no if test "$use_ccid_driver" != yes; then use_ccid_driver=no fi LIBUSB_LIBS="" fi if test "$have_libusb" = yes; then AC_DEFINE(HAVE_LIBUSB,1, [defined if libusb is available]) fi if test x"$usb_incdir" = x; then LIBUSB_CPPFLAGS="" else LIBUSB_CPPFLAGS="-I${usb_incdir}" fi fi AC_SUBST(LIBUSB_LIBS) AC_SUBST(LIBUSB_CPPFLAGS) # # Check whether it is necessary to link against libdl. # (For example to load libpcsclite) # gnupg_dlopen_save_libs="$LIBS" LIBS="" AC_SEARCH_LIBS(dlopen, c dl,,,) DL_LIBS=$LIBS AC_SUBST(DL_LIBS) LIBS="$gnupg_dlopen_save_libs" # Checks for g10 AC_ARG_ENABLE(sqlite, AS_HELP_STRING([--disable-sqlite], [disable the use of SQLITE]), try_sqlite=$enableval, try_sqlite=yes) if test x"$use_tofu" = xyes ; then if test x"$try_sqlite" = xyes ; then PKG_CHECK_MODULES([SQLITE3], [sqlite3 >= $NEED_SQLITE_VERSION], [have_sqlite=yes], [have_sqlite=no]) fi if test "$have_sqlite" = "yes"; then : AC_SUBST([SQLITE3_CFLAGS]) AC_SUBST([SQLITE3_LIBS]) else use_tofu=no tmp=$(echo "$SQLITE3_PKG_ERRORS" | tr '\n' '\v' | sed 's/\v/\n*** /g') AC_MSG_WARN([[ *** *** Building without SQLite support - TOFU and Keyboxd disabled *** *** $tmp]]) fi fi if test "$have_sqlite" != "yes"; then build_keyboxd=no fi AM_CONDITIONAL(SQLITE3, test "$have_sqlite" = "yes") if test x"$use_tofu" = xyes ; then AC_DEFINE(USE_TOFU, 1, [Enable to build the TOFU code]) fi # Checks for g13 AC_PATH_PROG(ENCFS, encfs, /usr/bin/encfs) AC_DEFINE_UNQUOTED(ENCFS, "${ENCFS}", [defines the filename of the encfs program]) AC_PATH_PROG(FUSERMOUNT, fusermount, /usr/bin/fusermount) AC_DEFINE_UNQUOTED(FUSERMOUNT, "${FUSERMOUNT}", [defines the filename of the fusermount program]) # # Check whether the nPth library is available # AM_PATH_NPTH("$NEED_NPTH_API:$NEED_NPTH_VERSION",have_npth=yes,have_npth=no) if test "$have_npth" = "yes"; then AC_DEFINE(HAVE_NPTH, 1, [Defined if the New Portable Thread Library is available]) AC_DEFINE(USE_NPTH, 1, [Defined if support for nPth is requested and nPth is available]) else AC_MSG_WARN([[ *** *** To support concurrent access for example in gpg-agent and the SCdaemon *** we need the support of the New Portable Threads Library. ***]]) fi # # Enable debugging of nPth # AC_ARG_ENABLE(npth-debug, AS_HELP_STRING([--enable-npth-debug], [build with debug version of npth]), [if test $enableval = yes ; then AC_DEFINE(NPTH_ENABLE_DEBUG,1, [Build with debug version of nPth]) fi]) # # NTBTLS is our TLS library. If it is not available we fall back to # GNUTLS. # AC_ARG_ENABLE(ntbtls, AS_HELP_STRING([--disable-ntbtls], [disable the use of NTBTLS as TLS library]), try_ntbtls=$enableval, try_ntbtls=yes) if test x"$try_ntbtls" = xyes ; then AM_PATH_NTBTLS("$NEED_NTBTLS_API:$NEED_NTBTLS_VERSION", [have_ntbtls=yes],[have_ntbtls=no]) fi if test "$have_ntbtls" = yes ; then use_tls_library=ntbtls AC_DEFINE(HTTP_USE_NTBTLS, 1, [Enable NTBTLS support in http.c]) else AC_ARG_ENABLE(gnutls, AS_HELP_STRING([--disable-gnutls], [disable GNUTLS as fallback TLS library]), try_gnutls=$enableval, try_gnutls=yes) if test x"$try_gnutls" = xyes ; then PKG_CHECK_MODULES([LIBGNUTLS], [gnutls >= $NEED_GNUTLS_VERSION], [have_gnutls=yes], [have_gnutls=no]) fi if test "$have_gnutls" = "yes"; then AC_SUBST([LIBGNUTLS_CFLAGS]) AC_SUBST([LIBGNUTLS_LIBS]) use_tls_library=gnutls AC_DEFINE(HTTP_USE_GNUTLS, 1, [Enable GNUTLS support in http.c]) else tmp=$(echo "$LIBGNUTLS_PKG_ERRORS" | tr '\n' '\v' | sed 's/\v/\n*** /g') build_dirmngr=no AC_MSG_WARN([[ *** *** Neither NTBTLS nor GNUTLS available - not building dirmngr. *** *** $tmp]]) fi fi # # Allow to set a fixed trust store file for system provided certificates. # AC_ARG_WITH([default-trust-store-file], [AS_HELP_STRING([--with-default-trust-store-file=FILE], [Use FILE as system trust store])], default_trust_store_file="$withval", default_trust_store_file="") if test x"$default_trust_store_file" = xno;then default_trust_store_file="" fi if test x"$default_trust_store_file" != x ; then AC_DEFINE_UNQUOTED([DEFAULT_TRUST_STORE_FILE], ["$default_trust_store_file"], [Use as default system trust store file]) fi AC_MSG_NOTICE([checking for networking options]) # # Must check for network library requirements before doing link tests # for ldap, for example. If ldap libs are static (or dynamic and without # ELF runtime link paths), then link will fail and LDAP support won't # be detected. # AC_CHECK_FUNC(gethostbyname, , AC_CHECK_LIB(nsl, gethostbyname, [NETLIBS="-lnsl $NETLIBS"])) AC_CHECK_FUNC(setsockopt, , AC_CHECK_LIB(socket, setsockopt, [NETLIBS="-lsocket $NETLIBS"])) # # Check standard resolver functions. # if test "$build_dirmngr" = "yes"; then _dns_save_libs=$LIBS LIBS="" # Find the system resolver which can always be enabled with # the dirmngr option --standard-resolver. # the double underscore thing is a glibc-ism? AC_SEARCH_LIBS(res_query,resolv bind,, AC_SEARCH_LIBS(__res_query,resolv bind,,have_resolver=no)) AC_SEARCH_LIBS(dn_expand,resolv bind,, AC_SEARCH_LIBS(__dn_expand,resolv bind,,have_resolver=no)) # macOS renames dn_skipname into res_9_dn_skipname in , # and for some reason fools us into believing we don't need # -lresolv even if we do. Since the test program checking for the # symbol does not include , we need to check for the # renamed symbol explicitly. AC_SEARCH_LIBS(res_9_dn_skipname,resolv bind,, AC_SEARCH_LIBS(dn_skipname,resolv bind,, AC_SEARCH_LIBS(__dn_skipname,resolv bind,,have_resolver=no))) if test x"$have_resolver" != xno ; then # Make sure that the BIND 4 resolver interface is workable before # enabling any code that calls it. At some point I'll rewrite the # code to use the BIND 8 resolver API. # We might also want to use libdns instead. AC_MSG_CHECKING([whether the resolver is usable]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include #include #include #include ]], [[unsigned char answer[PACKETSZ]; res_query("foo.bar",C_IN,T_A,answer,PACKETSZ); dn_skipname(0,0); dn_expand(0,0,0,0,0); ]])],have_resolver=yes,have_resolver=no) AC_MSG_RESULT($have_resolver) # This is Apple-specific and somewhat bizarre as they changed the # define in bind 8 for some reason. if test x"$have_resolver" != xyes ; then AC_MSG_CHECKING( [whether I can make the resolver usable with BIND_8_COMPAT]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[#define BIND_8_COMPAT #include #include #include #include ]], [[unsigned char answer[PACKETSZ]; res_query("foo.bar",C_IN,T_A,answer,PACKETSZ); dn_skipname(0,0); dn_expand(0,0,0,0,0); ]])],[have_resolver=yes ; need_compat=yes]) AC_MSG_RESULT($have_resolver) fi fi if test x"$have_resolver" = xyes ; then AC_DEFINE(HAVE_SYSTEM_RESOLVER,1,[The system's resolver is usable.]) DNSLIBS="$DNSLIBS $LIBS" if test x"$need_compat" = xyes ; then AC_DEFINE(BIND_8_COMPAT,1,[an Apple OSXism]) fi if test "$use_libdns" = yes; then show_tor_support=yes fi elif test "$use_libdns" = yes; then show_tor_support=yes else AC_MSG_WARN([[ *** *** The system's DNS resolver is not usable. *** Dirmngr functionality is limited. ***]]) show_tor_support="${show_tor_support} (no system resolver)" fi if test "$have_w32_system" = yes; then if test "$use_libdns" = yes; then DNSLIBS="$DNSLIBS -liphlpapi" fi fi LIBS=$_dns_save_libs fi AC_SUBST(DNSLIBS) # # Check for LDAP # # Note that running the check changes the variable # gnupg_have_ldap from "n/a" to "no" or "yes". AC_ARG_ENABLE(ldap, AS_HELP_STRING([--disable-ldap], [disable LDAP support]), [if test "$enableval" = "no"; then gnupg_have_ldap=no; fi]) if test "$gnupg_have_ldap" != "no" ; then if test "$build_dirmngr" = "yes" ; then GNUPG_CHECK_LDAP($NETLIBS) AC_CHECK_LIB(lber, ber_free, [ LBER_LIBS="$LBER_LIBS -llber" AC_DEFINE(HAVE_LBER,1, [defined if liblber is available]) have_lber=yes ]) fi fi AC_SUBST(LBER_LIBS) if test "$gnupg_have_ldap" = "no"; then AC_MSG_WARN([[ *** *** Building without LDAP support. *** No CRL access or X.509 certificate search available. ***]]) fi AM_CONDITIONAL(USE_LDAP, [test "$gnupg_have_ldap" = yes]) if test "$gnupg_have_ldap" = yes ; then AC_DEFINE(USE_LDAP,1,[Defined if LDAP is support]) fi # # Check for sendmail # # This isn't necessarily sendmail itself, but anything that gives a # sendmail-ish interface to the outside world. That includes Exim, # Postfix, etc. Basically, anything that can handle "sendmail -t". AC_ARG_WITH(mailprog, AS_HELP_STRING([--with-mailprog=NAME], [use "NAME -t" for mail transport]), ,with_mailprog=yes) if test x"$with_mailprog" = xyes ; then AC_PATH_PROG(SENDMAIL,sendmail,,"$PATH":/usr/sbin:/usr/libexec:/usr/lib) elif test x"$with_mailprog" != xno ; then AC_MSG_CHECKING([for a mail transport program]) AC_SUBST(SENDMAIL,$with_mailprog) AC_MSG_RESULT($with_mailprog) fi AC_DEFINE_UNQUOTED(NAME_OF_SENDMAIL,"$SENDMAIL", [Tool with sendmail -t interface]) # # Construct a printable name of the OS # case "${host}" in *-mingw32ce*) PRINTABLE_OS_NAME="W32CE" ;; *-mingw32*) PRINTABLE_OS_NAME="MingW32" ;; *-*-cygwin*) PRINTABLE_OS_NAME="Cygwin" ;; i?86-emx-os2 | i?86-*-os2*emx ) PRINTABLE_OS_NAME="OS/2" ;; i?86-*-msdosdjgpp*) PRINTABLE_OS_NAME="MSDOS/DJGPP" try_dynload=no ;; *-linux*) PRINTABLE_OS_NAME="GNU/Linux" ;; *) PRINTABLE_OS_NAME=`uname -s || echo "Unknown"` ;; esac AC_DEFINE_UNQUOTED(PRINTABLE_OS_NAME, "$PRINTABLE_OS_NAME", [A human readable text with the name of the OS]) # # Checking for iconv # if test "$require_iconv" = yes; then AM_ICONV else LIBICONV= LTLIBICONV= AC_SUBST(LIBICONV) AC_SUBST(LTLIBICONV) fi # # Check for gettext # # This is "GNU gnupg" - The project-id script from gettext # needs this string # AC_MSG_NOTICE([checking for gettext]) AM_PO_SUBDIRS AM_GNU_GETTEXT_VERSION([0.17]) if test "$try_gettext" = yes; then AM_GNU_GETTEXT([external],[need-ngettext]) # gettext requires some extra checks. These really should be part of # the basic AM_GNU_GETTEXT macro. TODO: move other gettext-specific # function checks to here. AC_CHECK_FUNCS(strchr) else USE_NLS=no USE_INCLUDED_LIBINTL=no BUILD_INCLUDED_LIBINTL=no POSUB=po AC_SUBST(USE_NLS) AC_SUBST(USE_INCLUDED_LIBINTL) AC_SUBST(BUILD_INCLUDED_LIBINTL) AC_SUBST(POSUB) fi # We use HAVE_LANGINFO_CODESET in a couple of places. AM_LANGINFO_CODESET # Checks required for our use of locales gt_LC_MESSAGES # # SELinux support # if test "$selinux_support" = yes ; then AC_DEFINE(ENABLE_SELINUX_HACKS,1,[Define to enable SELinux support]) fi # # Checks for header files. # AC_MSG_NOTICE([checking for header files]) -AC_HEADER_STDC -AC_HEADER_TIME AC_CHECK_HEADERS([unistd.h langinfo.h termio.h locale.h \ - pwd.h inttypes.h signal.h sys/select.h \ + pwd.h inttypes.h signal.h sys/select.h sys/time.h \ stdint.h signal.h termios.h \ ucred.h sys/ucred.h sys/sysmacros.h sys/mkdev.h]) # # Checks for typedefs, structures, and compiler characteristics. # AC_MSG_NOTICE([checking for system characteristics]) AC_C_CONST AC_C_INLINE AC_C_VOLATILE AC_TYPE_SIZE_T AC_TYPE_MODE_T AC_CHECK_FUNCS([sigdescr_np]) AC_CHECK_DECLS([sys_siglist],[],[],[#include /* NetBSD declares sys_siglist in unistd.h. */ #ifdef HAVE_UNISTD_H # include #endif ]) gl_TYPE_SOCKLEN_T AC_SEARCH_LIBS([inet_addr], [nsl]) AC_ARG_ENABLE(endian-check, AS_HELP_STRING([--disable-endian-check], [disable the endian check and trust the OS provided macros]), endiancheck=$enableval,endiancheck=yes) if test x"$endiancheck" = xyes ; then GNUPG_CHECK_ENDIAN fi # fixme: we should get rid of the byte type AC_CHECK_TYPES([byte, ushort, ulong, u16, u32]) AC_CHECK_SIZEOF(unsigned short) AC_CHECK_SIZEOF(unsigned int) AC_CHECK_SIZEOF(unsigned long) AC_CHECK_SIZEOF(unsigned long long) AC_CHECK_SIZEOF(size_t) AC_CHECK_SIZEOF(time_t,,[[ #include -#if TIME_WITH_SYS_TIME +#ifdef HAVE_SYS_TIME_H # include -# include #else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif +# include #endif ]]) GNUPG_TIME_T_UNSIGNED if test "$ac_cv_sizeof_unsigned_short" = "0" \ || test "$ac_cv_sizeof_unsigned_int" = "0" \ || test "$ac_cv_sizeof_unsigned_long" = "0"; then AC_MSG_WARN([Hmmm, something is wrong with the sizes - using defaults]); fi # # Checks for library functions. # AC_MSG_NOTICE([checking for library functions]) AC_CHECK_DECLS(getpagesize) AC_FUNC_FSEEKO AC_FUNC_VPRINTF AC_FUNC_FORK AC_CHECK_FUNCS([atexit canonicalize_file_name clock_gettime ctermid \ explicit_bzero fcntl flockfile fsync ftello \ ftruncate funlockfile getaddrinfo getenv getpagesize \ getpwnam getpwuid getrlimit getrusage gettimeofday \ gmtime_r inet_ntop inet_pton isascii lstat memicmp \ memmove memrchr mmap nl_langinfo pipe raise rand \ setenv setlocale setrlimit sigaction sigprocmask \ stat stpcpy strcasecmp strerror strftime stricmp \ strlwr strncasecmp strpbrk strsep strtol strtoul \ strtoull tcgetattr timegm times ttyname unsetenv \ wait4 waitpid ]) # On some systems (e.g. Solaris) nanosleep requires linking to librl. # Given that we use nanosleep only as an optimization over a select # based wait function we want it only if it is available in libc. _save_libs="$LIBS" AC_SEARCH_LIBS([nanosleep], [], [AC_DEFINE(HAVE_NANOSLEEP,1, [Define to 1 if you have the `nanosleep' function in libc.])]) LIBS="$_save_libs" # See whether libc supports the Linux inotify interface case "${host}" in *-*-linux*) AC_CHECK_FUNCS([inotify_init]) ;; esac if test "$have_android_system" = yes; then # On Android ttyname is a stub but prints an error message. AC_DEFINE(HAVE_BROKEN_TTYNAME,1, [Defined if ttyname does not work properly]) fi AC_CHECK_TYPES([struct sigaction, sigset_t],,,[#include ]) # Dirmngr requires mmap on Unix systems. if test $ac_cv_func_mmap != yes -a $mmap_needed = yes; then AC_MSG_ERROR([[Sorry, the current implementation requires mmap.]]) fi # # Check for the getsockopt SO_PEERCRED, etc. # AC_CHECK_MEMBERS([struct ucred.pid, struct ucred.cr_pid, struct sockpeercred.pid], [], [], [#include #include ]) # (Open)Solaris AC_CHECK_FUNCS([getpeerucred]) # # W32 specific test # # # Do we have zlib? Must do it here because Solaris failed # when compiling a conftest (due to the "-lz" from LIBS). # Note that we combine zlib and bzlib2 in ZLIBS. # if test "$use_zip" = yes ; then _cppflags="${CPPFLAGS}" _ldflags="${LDFLAGS}" AC_ARG_WITH(zlib, [ --with-zlib=DIR use libz in DIR],[ if test -d "$withval"; then CPPFLAGS="${CPPFLAGS} -I$withval/include" LDFLAGS="${LDFLAGS} -L$withval/lib" fi ]) AC_CHECK_HEADER(zlib.h, AC_CHECK_LIB(z, deflateInit2_, [ ZLIBS="-lz" AC_DEFINE(HAVE_ZIP,1, [Defined if ZIP and ZLIB are supported]) ], CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}), CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}) fi # # Check whether we can support bzip2 # if test "$use_bzip2" = yes ; then _cppflags="${CPPFLAGS}" _ldflags="${LDFLAGS}" AC_ARG_WITH(bzip2, AS_HELP_STRING([--with-bzip2=DIR],[look for bzip2 in DIR]), [ if test -d "$withval" ; then CPPFLAGS="${CPPFLAGS} -I$withval/include" LDFLAGS="${LDFLAGS} -L$withval/lib" fi ],withval="") # Checking alongside stdio.h as an early version of bzip2 (1.0) # required stdio.h to be included before bzlib.h, and Solaris 9 is # woefully out of date. if test "$withval" != no ; then AC_CHECK_HEADER(bzlib.h, AC_CHECK_LIB(bz2,BZ2_bzCompressInit, [ have_bz2=yes ZLIBS="$ZLIBS -lbz2" AC_DEFINE(HAVE_BZIP2,1, [Defined if the bz2 compression library is available]) ], CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}), CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags},[#include ]) fi fi AM_CONDITIONAL(ENABLE_BZIP2_SUPPORT,test x"$have_bz2" = "xyes") AC_SUBST(ZLIBS) # Check for readline support GNUPG_CHECK_READLINE if test "$development_version" = yes; then AC_DEFINE(IS_DEVELOPMENT_VERSION,1, [Defined if this is not a regular release]) fi if test "$USE_MAINTAINER_MODE" = "yes"; then AC_DEFINE(MAINTAINER_MODE,1, [Defined if this build is in maintainer mode]) fi AM_CONDITIONAL(CROSS_COMPILING, test x$cross_compiling = xyes) GNUPG_CHECK_GNUMAKE # Add some extra libs here so that previous tests don't fail for # mysterious reasons - the final link step should bail out. # W32SOCKLIBS is also defined so that if can be used for tools not # requiring any network stuff but linking to code in libcommon which # tracks in winsock stuff (e.g. init_common_subsystems). if test "$have_w32_system" = yes; then if test "$have_w32ce_system" = yes; then W32SOCKLIBS="-lws2" else W32SOCKLIBS="-lws2_32" fi NETLIBS="${NETLIBS} ${W32SOCKLIBS}" fi AC_SUBST(NETLIBS) AC_SUBST(W32SOCKLIBS) # # TPM libtss library .. don't compile TPM support if we don't have it # AC_ARG_WITH([tss], [AS_HELP_STRING([--with-tss=TSS], [use the specified TPM Software Stack (ibm, intel, or autodetect)])], [with_tss=$withval], [with_tss=autodetect]) LIBTSS_LIBS= LIBTSS_CFLAGS= if test "$build_tpm2d" = "yes"; then _save_libs="$LIBS" _save_cflags="$CFLAGS" LIBS="" if test "$with_tss" = autodetect; then AC_SEARCH_LIBS([TSS_Create],[tss ibmtss],have_libtss=IBM, AC_SEARCH_LIBS([Esys_Initialize],[tss2-esys],have_libtss=Intel,have_libtss=no)) elif test "$with_tss" = ibm; then AC_SEARCH_LIBS([TSS_Create],[tss ibmtss],have_libtss=IBM, [AC_MSG_ERROR([IBM TPM Software Stack requested but not found])]) elif test "$with_tss" = intel; then AC_SEARCH_LIBS([Esys_Initialize],[tss2-esys],have_libtss=Intel, [AC_MSG_ERROR([Intel TPM Software Stack requested but not found])]) else AC_MSG_ERROR([Invalid TPM Software Stack requested: $with_tss]) fi if test "$have_libtss" = IBM; then LIBTSS_CFLAGS="-DTPM_POSIX" CFLAGS="$CFLAGS ${LIBTSS_CFLAGS}" AC_CHECK_HEADER([tss2/tss.h], [AC_DEFINE(TSS_INCLUDE,tss2, [tss2 include location])], [ AC_CHECK_HEADER([ibmtss/tss.h],[AC_DEFINE(TSS_INCLUDE,ibmtss, [ibmtss include location])], [ AC_MSG_WARN([No TSS2 include directory found, disabling TPM support]) have_libtss=no ]) ]) LIBTSS_LIBS=$LIBS AC_SUBST(TSS_INCLUDE) elif test "$have_libtss" = Intel; then ## # Intel TSS has an API issue: Esys_TR_GetTpmHandle wasn't introduced # until version 2.4.0. # # Note: the missing API is fairly serious and is also easily backportable # so keep the check below as is intead of going by library version number. ## AC_CHECK_LIB(tss2-esys, Esys_TR_GetTpmHandle, [], [ AC_MSG_WARN([Need Esys_TR_GetTpmHandle API (usually requires Intel TSS 2.4.0 or later, disabling TPM support)]) have_libtss=no ]) LIBTSS_LIBS="$LIBS -ltss2-mu -ltss2-rc -ltss2-tctildr" AC_DEFINE(HAVE_INTEL_TSS, 1, [Defined if we have the Intel TSS]) fi LIBS="$_save_libs" CFLAGS="$_save_cflags" if test "$have_libtss" != no; then AC_DEFINE(HAVE_LIBTSS, 1, [Defined if we have TPM2 support library]) # look for a TPM emulator for testing AC_PATH_PROG(TPMSERVER, tpm_server,,/bin:/usr/bin:/usr/lib/ibmtss:/usr/libexec/ibmtss) AC_PATH_PROG(SWTPM, swtpm,,/bin:/usr/bin:/usr/lib/ibmtss:/usr/libexec/ibmtss) AC_PATH_PROG(SWTPM_IOCTL, swtpm_ioctl,,/bin:/usr/bin:/usr/lib/ibmtss:/usr/libexec/ibmtss) AC_PATH_PROG(TSSSTARTUP, tssstartup,,/bin:/usr/bin:/usr/lib/ibmtss:/usr/libexec/ibmtss) fi fi if test "$have_libtss" = no; then build_tpm2d=no fi AC_SUBST(LIBTSS_LIBS) AC_SUBST(LIBTSS_CFLAGS) AM_CONDITIONAL(HAVE_LIBTSS, test "$have_libtss" != no) AM_CONDITIONAL(TEST_LIBTSS, test -n "$TPMSERVER" || test -n "$SWTPM" && test -n "$TSSSTARTUP") AC_SUBST(HAVE_LIBTSS) # # Setup gcc specific options # USE_C99_CFLAGS= AC_MSG_NOTICE([checking for cc features]) if test "$GCC" = yes; then mycflags= mycflags_save=$CFLAGS # Check whether gcc does not emit a diagnositc for unknown -Wno-* # options. This is the case for gcc >= 4.6 AC_MSG_CHECKING([if gcc ignores unknown -Wno-* options]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6 ) #kickerror #endif]],[])],[_gcc_silent_wno=yes],[_gcc_silent_wno=no]) AC_MSG_RESULT($_gcc_silent_wno) # Note that it is okay to use CFLAGS here because these are just # warning options and the user should have a chance of overriding # them. if test "$USE_MAINTAINER_MODE" = "yes"; then mycflags="$mycflags -O3 -Wall -Wcast-align -Wshadow -Wstrict-prototypes" mycflags="$mycflags -Wformat -Wno-format-y2k -Wformat-security" if test x"$_gcc_silent_wno" = xyes ; then _gcc_wopt=yes else AC_MSG_CHECKING([if gcc supports -Wno-missing-field-initializers]) CFLAGS="-Wno-missing-field-initializers" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [_gcc_wopt=yes],[_gcc_wopt=no]) AC_MSG_RESULT($_gcc_wopt) fi if test x"$_gcc_wopt" = xyes ; then mycflags="$mycflags -W -Wno-sign-compare -Wno-format-zero-length" mycflags="$mycflags -Wno-missing-field-initializers" fi AC_MSG_CHECKING([if gcc supports -Wdeclaration-after-statement]) CFLAGS="-Wdeclaration-after-statement" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no) AC_MSG_RESULT($_gcc_wopt) if test x"$_gcc_wopt" = xyes ; then mycflags="$mycflags -Wdeclaration-after-statement" fi AC_MSG_CHECKING([if gcc supports -Wlogical-op]) CFLAGS="-Wlogical-op -Werror" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no) AC_MSG_RESULT($_gcc_wopt) if test x"$_gcc_wopt" = xyes ; then mycflags="$mycflags -Wlogical-op" fi AC_MSG_CHECKING([if gcc supports -Wvla]) CFLAGS="-Wvla" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no) AC_MSG_RESULT($_gcc_wopt) if test x"$_gcc_wopt" = xyes ; then mycflags="$mycflags -Wvla" fi else mycflags="$mycflags -Wall" if test x"$_gcc_silent_wno" = xyes ; then mycflags="$mycflags -Wno-format-zero-length" fi fi if test x"$_gcc_silent_wno" = xyes ; then _gcc_psign=yes else AC_MSG_CHECKING([if gcc supports -Wno-pointer-sign]) CFLAGS="-Wno-pointer-sign" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [_gcc_psign=yes],[_gcc_psign=no]) AC_MSG_RESULT($_gcc_psign) fi if test x"$_gcc_psign" = xyes ; then mycflags="$mycflags -Wno-pointer-sign" fi AC_MSG_CHECKING([if gcc supports -Wpointer-arith]) CFLAGS="-Wpointer-arith" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_psign=yes,_gcc_psign=no) AC_MSG_RESULT($_gcc_psign) if test x"$_gcc_psign" = xyes ; then mycflags="$mycflags -Wpointer-arith" fi CFLAGS="$mycflags $mycflags_save" if test "$use_libdns" = yes; then # dirmngr/dns.{c,h} require C99 and GNU extensions. */ USE_C99_CFLAGS="-std=gnu99" fi fi AC_SUBST(USE_C99_CFLAGS) # # This is handy for debugging so the compiler doesn't rearrange # things and eliminate variables. # AC_ARG_ENABLE(optimization, AS_HELP_STRING([--disable-optimization], [disable compiler optimization]), [if test $enableval = no ; then CFLAGS=`echo $CFLAGS | sed s/-O[[1-9]]\ /-O0\ /g` fi]) # # log_debug has certain requirements which might hamper portability. # Thus we use an option to enable it. # AC_MSG_CHECKING([whether to enable log_clock]) AC_ARG_ENABLE(log_clock, AS_HELP_STRING([--enable-log-clock], [enable log_clock timestamps]), enable_log_clock=$enableval, enable_log_clock=no) AC_MSG_RESULT($enable_log_clock) if test "$enable_log_clock" = yes ; then AC_DEFINE(ENABLE_LOG_CLOCK,1,[Defined to use log_clock timestamps]) fi # Add -Werror to CFLAGS. This hack can be used to avoid problems with # misbehaving autoconf tests in case the user supplied -Werror. # AC_ARG_ENABLE(werror, AS_HELP_STRING([--enable-werror], [append -Werror to CFLAGS]), [if test $enableval = yes ; then CFLAGS="$CFLAGS -Werror" fi]) # # Configure option --enable-all-tests # AC_MSG_CHECKING([whether "make check" shall run all tests]) AC_ARG_ENABLE(all-tests, AS_HELP_STRING([--enable-all-tests], [let "make check" run all tests]), run_all_tests=$enableval, run_all_tests=no) AC_MSG_RESULT($run_all_tests) if test "$run_all_tests" = "yes"; then AC_DEFINE(RUN_ALL_TESTS,1, [Defined if "make check" shall run all tests]) fi # # Configure option --disable-tests # AC_MSG_CHECKING([whether tests should be run]) AC_ARG_ENABLE(tests, AS_HELP_STRING([--disable-tests], [do not run any tests]), run_tests=$enableval, run_tests=yes) AC_MSG_RESULT($run_tests) # # We do not want support for the GNUPG_BUILDDIR environment variable # in a released version. However, our regression tests suite requires # this and thus we build with support for it during "make distcheck". # This configure option implements this along with the top Makefile's # AM_DISTCHECK_CONFIGURE_FLAGS. # gnupg_builddir_envvar=no AC_ARG_ENABLE(gnupg-builddir-envvar,, gnupg_builddir_envvar=$enableval) if test x"$gnupg_builddir_envvar" = x"yes"; then AC_DEFINE(ENABLE_GNUPG_BUILDDIR_ENVVAR, 1, [This is only used with "make distcheck"]) fi # # To avoid problems with systemd cleaning up the /run/user directory, # this option will make GnuPG try to use /run/gnupg/user as socket dir # before /run/user # AC_ARG_ENABLE(run-gnupg-user-socket, AS_HELP_STRING([--enable-run-gnupg-user-socket], [try /run/gnupg/user for sockets prior to /run/user]), use_run_gnupg_user_socket=$enableval) if test x"$use_run_gnupg_user_socket" = x"yes"; then AC_DEFINE(USE_RUN_GNUPG_USER_SOCKET, 1, [If defined try /run/gnupg/user before /run/user]) fi # # Decide what to build # build_scdaemon_extra="" if test "$build_scdaemon" = "yes"; then if test $have_libusb = no; then build_scdaemon_extra="without internal CCID driver" fi if test -n "$build_scdaemon_extra"; then build_scdaemon_extra="(${build_scdaemon_extra})" fi fi # # Set variables for use by automake makefiles. # AM_CONDITIONAL(BUILD_GPG, test "$build_gpg" = "yes") AM_CONDITIONAL(BUILD_GPGSM, test "$build_gpgsm" = "yes") AM_CONDITIONAL(BUILD_AGENT, test "$build_agent" = "yes") AM_CONDITIONAL(BUILD_SCDAEMON, test "$build_scdaemon" = "yes") AM_CONDITIONAL(BUILD_G13, test "$build_g13" = "yes") AM_CONDITIONAL(BUILD_DIRMNGR, test "$build_dirmngr" = "yes") AM_CONDITIONAL(BUILD_KEYBOXD, test "$build_keyboxd" = "yes") AM_CONDITIONAL(BUILD_TPM2D, test "$build_tpm2d" = "yes") AM_CONDITIONAL(BUILD_DOC, test "$build_doc" = "yes") AM_CONDITIONAL(BUILD_GPGTAR, test "$build_gpgtar" = "yes") AM_CONDITIONAL(BUILD_WKS_TOOLS, test "$build_wks_tools" = "yes") AM_CONDITIONAL(DISABLE_TESTS, test "$run_tests" != yes) AM_CONDITIONAL(ENABLE_CARD_SUPPORT, test "$card_support" = yes) AM_CONDITIONAL(NO_TRUST_MODELS, test "$use_trust_models" = no) AM_CONDITIONAL(USE_TOFU, test "$use_tofu" = yes) # # Set some defines for use gpgconf. # if test "$build_gpg" = yes ; then AC_DEFINE(BUILD_WITH_GPG,1,[Defined if GPG is to be build]) fi if test "$build_gpgsm" = yes ; then AC_DEFINE(BUILD_WITH_GPGSM,1,[Defined if GPGSM is to be build]) fi if test "$build_agent" = yes ; then AC_DEFINE(BUILD_WITH_AGENT,1,[Defined if GPG-AGENT is to be build]) fi if test "$build_scdaemon" = yes ; then AC_DEFINE(BUILD_WITH_SCDAEMON,1,[Defined if SCDAEMON is to be build]) fi if test "$build_dirmngr" = yes ; then AC_DEFINE(BUILD_WITH_DIRMNGR,1,[Defined if DIRMNGR is to be build]) fi if test "$build_keyboxd" = yes ; then AC_DEFINE(BUILD_WITH_KEYBOXD,1,[Defined if KEYBOXD is to be build]) fi if test "$build_tpm2d" = yes ; then AC_DEFINE(BUILD_WITH_TPM2D,1,[Defined if TPM2D to be build]) fi if test "$build_g13" = yes ; then AC_DEFINE(BUILD_WITH_G13,1,[Defined if G13 is to be build]) fi # # Define Name strings # AC_DEFINE_UNQUOTED(GNUPG_NAME, "GnuPG", [The name of the project]) AC_DEFINE_UNQUOTED(GPG_NAME, "gpg", [The name of the OpenPGP tool]) AC_DEFINE_UNQUOTED(GPG_DISP_NAME, "GnuPG", [The displayed name of gpg]) AC_DEFINE_UNQUOTED(GPGSM_NAME, "gpgsm", [The name of the S/MIME tool]) AC_DEFINE_UNQUOTED(GPGSM_DISP_NAME, "GPGSM", [The displayed name of gpgsm]) AC_DEFINE_UNQUOTED(GPG_AGENT_NAME, "gpg-agent", [The name of the agent]) AC_DEFINE_UNQUOTED(GPG_AGENT_DISP_NAME, "GPG Agent", [The displayed name of gpg-agent]) AC_DEFINE_UNQUOTED(TPM2DAEMON_NAME, "tpm2daemon", [The name of the TPM2 daemon]) AC_DEFINE_UNQUOTED(TPM2DAEMON_DISP_NAME, "TPM2 Daemon", [The displayed name of TPM2 daemon]) AC_DEFINE_UNQUOTED(SCDAEMON_NAME, "scdaemon", [The name of the scdaemon]) AC_DEFINE_UNQUOTED(SCDAEMON_DISP_NAME, "SCDaemon", [The displayed name of scdaemon]) AC_DEFINE_UNQUOTED(DIRMNGR_NAME, "dirmngr", [The name of the dirmngr]) AC_DEFINE_UNQUOTED(DIRMNGR_DISP_NAME, "DirMngr", [The displayed name of dirmngr]) AC_DEFINE_UNQUOTED(KEYBOXD_NAME, "keyboxd", [The name of the keyboxd]) AC_DEFINE_UNQUOTED(KEYBOXD_DISP_NAME, "Keyboxd", [The displayed name of keyboxd]) AC_DEFINE_UNQUOTED(G13_NAME, "g13", [The name of the g13 tool]) AC_DEFINE_UNQUOTED(G13_DISP_NAME, "G13", [The displayed name of g13]) AC_DEFINE_UNQUOTED(GPGCONF_NAME, "gpgconf", [The name of the gpgconf tool]) AC_DEFINE_UNQUOTED(GPGCONF_DISP_NAME, "GPGConf", [The displayed name of gpgconf]) AC_DEFINE_UNQUOTED(GPGTAR_NAME, "gpgtar", [The name of the gpgtar tool]) AC_DEFINE_UNQUOTED(GPG_AGENT_SOCK_NAME, "S.gpg-agent", [The name of the agent socket]) AC_DEFINE_UNQUOTED(GPG_AGENT_EXTRA_SOCK_NAME, "S.gpg-agent.extra", [The name of the agent socket for remote access]) AC_DEFINE_UNQUOTED(GPG_AGENT_BROWSER_SOCK_NAME, "S.gpg-agent.browser", [The name of the agent socket for browsers]) AC_DEFINE_UNQUOTED(GPG_AGENT_SSH_SOCK_NAME, "S.gpg-agent.ssh", [The name of the agent socket for ssh]) AC_DEFINE_UNQUOTED(DIRMNGR_INFO_NAME, "DIRMNGR_INFO", [The name of the dirmngr info envvar]) AC_DEFINE_UNQUOTED(SCDAEMON_SOCK_NAME, "S.scdaemon", [The name of the SCdaemon socket]) AC_DEFINE_UNQUOTED(KEYBOXD_SOCK_NAME, "S.keyboxd", [The name of the keyboxd socket]) AC_DEFINE_UNQUOTED(TPM2DAEMON_SOCK_NAME, "S.tpm2daemon", [The name of the TPM2 daemon socket]) AC_DEFINE_UNQUOTED(DIRMNGR_SOCK_NAME, "S.dirmngr", [The name of the dirmngr socket]) AC_DEFINE_UNQUOTED(DIRMNGR_DEFAULT_KEYSERVER, "hkps://keyserver.ubuntu.com", [The default keyserver for dirmngr to use, if none is explicitly given]) AC_DEFINE_UNQUOTED(GPGEXT_GPG, "gpg", [The standard binary file suffix]) if test "$have_w32_system" = yes; then AC_DEFINE_UNQUOTED(GNUPG_REGISTRY_DIR, "Software\\\\GNU\\\\GnuPG", [The directory part of the W32 registry keys]) fi # # Provide information about the build. # BUILD_REVISION="mym4_revision" AC_SUBST(BUILD_REVISION) AC_DEFINE_UNQUOTED(BUILD_REVISION, "$BUILD_REVISION", [GIT commit id revision used to build this package]) changequote(,)dnl BUILD_VERSION=`echo "$VERSION" | sed 's/\([0-9.]*\).*/\1./'` changequote([,])dnl BUILD_VERSION="${BUILD_VERSION}mym4_revision_dec" BUILD_FILEVERSION=`echo "${BUILD_VERSION}" | tr . ,` AC_SUBST(BUILD_VERSION) AC_SUBST(BUILD_FILEVERSION) AC_ARG_ENABLE([build-timestamp], AS_HELP_STRING([--enable-build-timestamp], [set an explicit build timestamp for reproducibility. (default is the current time in ISO-8601 format)]), [if test "$enableval" = "yes"; then BUILD_TIMESTAMP=`date -u +%Y-%m-%dT%H:%M+0000 2>/dev/null || date` else BUILD_TIMESTAMP="$enableval" fi BUILD_HOSTNAME="$ac_hostname"], [BUILD_TIMESTAMP="" BUILD_HOSTNAME=""]) AC_SUBST(BUILD_TIMESTAMP) AC_DEFINE_UNQUOTED(BUILD_TIMESTAMP, "$BUILD_TIMESTAMP", [The time this package was configured for a build]) AC_SUBST(BUILD_HOSTNAME) # # Print errors here so that they are visible all # together and the user can acquire them all together. # die=no if test "$have_gpg_error" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libgpg-error to build this program. ** This library is for example available at *** https://gnupg.org/ftp/gcrypt/gpgrt *** (at least version $NEED_GPGRT_VERSION is required.) ***]]) fi if test "$have_libgcrypt" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libgcrypt to build this program. ** This library is for example available at *** https://gnupg.org/ftp/gcrypt/libgcrypt/ *** (at least version $NEED_LIBGCRYPT_VERSION (API $NEED_LIBGCRYPT_API) is required.) ***]]) fi if test "$have_libassuan" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libassuan to build this program. *** This library is for example available at *** https://gnupg.org/ftp/gcrypt/libassuan/ *** (at least version $NEED_LIBASSUAN_VERSION (API $NEED_LIBASSUAN_API) is required). ***]]) fi if test "$have_ksba" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libksba to build this program. *** This library is for example available at *** https://gnupg.org/ftp/gcrypt/libksba/ *** (at least version $NEED_KSBA_VERSION using API $NEED_KSBA_API is required). ***]]) fi if test "$gnupg_have_ldap" = yes; then if test "$have_w32ce_system" = yes; then AC_MSG_NOTICE([[ *** Note that CeGCC might be broken, a package fixing this is: *** http://files.kolab.org/local/windows-ce/ *** source/wldap32_0.1-mingw32ce.orig.tar.gz *** binary/wldap32-ce-arm-dev_0.1-1_all.deb ***]]) fi fi if test "$have_npth" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** It is now required to build with support for the *** New Portable Threads Library (nPth). Please install this *** library first. The library is for example available at *** https://gnupg.org/ftp/gcrypt/npth/ *** (at least version $NEED_NPTH_VERSION (API $NEED_NPTH_API) is required). ***]]) fi if test "$require_iconv" = yes; then if test "$am_func_iconv" != yes; then die=yes AC_MSG_NOTICE([[ *** *** The system does not provide a working iconv function. Please *** install a suitable library; for example GNU Libiconv which is *** available at: *** https://ftp.gnu.org/gnu/libiconv/ ***]]) fi fi if test "$use_ccid_driver" = yes; then if test "$have_libusb" != yes; then die=yes AC_MSG_NOTICE([[ *** *** You need libusb to build the internal ccid driver. Please *** install a libusb suitable for your system. ***]]) fi fi if test "$die" = "yes"; then AC_MSG_ERROR([[ *** *** Required libraries not found. Please consult the above messages *** and install them before running configure again. ***]]) fi AC_CONFIG_FILES([ m4/Makefile Makefile po/Makefile.in common/Makefile common/w32info-rc.h regexp/Makefile kbx/Makefile g10/Makefile sm/Makefile agent/Makefile scd/Makefile tpm2d/Makefile g13/Makefile dirmngr/Makefile tools/Makefile doc/Makefile tests/Makefile tests/gpgscm/Makefile tests/openpgp/Makefile tests/cms/Makefile tests/migrations/Makefile tests/tpm2dtests/Makefile tests/gpgme/Makefile tests/pkits/Makefile g10/gpg.w32-manifest tools/gpg-connect-agent.w32-manifest tools/gpgconf.w32-manifest tools/gpgtar.w32-manifest tools/gpg-check-pattern.w32-manifest tools/gpg-wks-client.w32-manifest tools/gpg-card.w32-manifest ]) AC_OUTPUT show_tss_type= if test "$build_tpm2d" = "yes"; then show_tss_type="($have_libtss)" fi echo " GnuPG v${VERSION} has been configured as follows: Revision: mym4_revision (mym4_revision_dec) Platform: $PRINTABLE_OS_NAME ($host) OpenPGP: $build_gpg S/MIME: $build_gpgsm Agent: $build_agent Smartcard: $build_scdaemon $build_scdaemon_extra TPM: $build_tpm2d $show_tss_type G13: $build_g13 Dirmngr: $build_dirmngr Keyboxd: $build_keyboxd Gpgtar: $build_gpgtar WKS tools: $build_wks_tools Protect tool: $show_gnupg_protect_tool_pgm LDAP wrapper: $show_gnupg_dirmngr_ldap_pgm Default agent: $show_gnupg_agent_pgm Default pinentry: $show_gnupg_pinentry_pgm Default scdaemon: $show_gnupg_scdaemon_pgm Default keyboxd: $show_gnupg_keyboxd_pgm Default tpm2daemon: $show_gnupg_tpm2daemon_pgm Default dirmngr: $show_gnupg_dirmngr_pgm Dirmngr auto start: $dirmngr_auto_start Readline support: $gnupg_cv_have_readline LDAP support: $gnupg_have_ldap TLS support: $use_tls_library TOFU support: $use_tofu Tor support: $show_tor_support " if test "$have_libtss" != no -a -z "$TPMSERVER" -a -z "$SWTPM"; then cat < /* INT_MAX */ #include /* va_list va_start va_end */ #include /* offsetof() */ #ifdef _WIN32 /* JW: This breaks our mingw build: #define uint32_t unsigned int */ #else #include /* uint32_t */ #endif #include /* malloc(3) realloc(3) free(3) rand(3) random(3) arc4random(3) */ #include /* FILE fopen(3) fclose(3) getc(3) rewind(3) vsnprintf(3) */ #include /* memcpy(3) strlen(3) memmove(3) memchr(3) memcmp(3) strchr(3) strsep(3) strcspn(3) */ #include /* strcasecmp(3) strncasecmp(3) */ #include /* isspace(3) isdigit(3) */ #include /* time_t time(2) difftime(3) */ #include /* SIGPIPE sigemptyset(3) sigaddset(3) sigpending(2) sigprocmask(2) pthread_sigmask(3) sigtimedwait(2) */ #include /* errno EINVAL ENOENT */ #undef NDEBUG #include /* assert(3) */ #if _WIN32 #ifndef FD_SETSIZE #define FD_SETSIZE 1024 #endif #include #include typedef SOCKET socket_fd_t; #define STDCALL __stdcall -#ifdef TIME_WITH_SYS_TIME +#ifdef HAVE_SYS_TIME_H #include /* gettimeofday(2) */ #endif #else typedef int socket_fd_t; #define STDCALL #include /* gettimeofday(2) */ #include /* FD_SETSIZE socklen_t */ #include /* FD_ZERO FD_SET fd_set select(2) */ #include /* AF_INET AF_INET6 AF_UNIX struct sockaddr struct sockaddr_in struct sockaddr_in6 socket(2) */ #if defined(AF_UNIX) #include /* struct sockaddr_un */ #endif #include /* F_SETFD F_GETFL F_SETFL O_NONBLOCK fcntl(2) */ #include /* _POSIX_THREADS gethostname(3) close(2) */ #include /* POLLIN POLLOUT */ #include /* struct sockaddr_in struct sockaddr_in6 */ #include /* inet_pton(3) inet_ntop(3) htons(3) ntohs(3) */ #include /* struct addrinfo */ #endif #include "gpgrt.h" /* For GGPRT_GCC_VERSION */ #include "dns.h" /* * C O M P I L E R V E R S I O N & F E A T U R E D E T E C T I O N * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_GNUC_2VER(M, m, p) (((M) * 10000) + ((m) * 100) + (p)) #define DNS_GNUC_PREREQ(M, m, p) (__GNUC__ > 0 && DNS_GNUC_2VER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) >= DNS_GNUC_2VER((M), (m), (p))) #define DNS_MSC_2VER(M, m, p) ((((M) + 6) * 10000000) + ((m) * 1000000) + (p)) #define DNS_MSC_PREREQ(M, m, p) (_MSC_VER_FULL > 0 && _MSC_VER_FULL >= DNS_MSC_2VER((M), (m), (p))) #define DNS_SUNPRO_PREREQ(M, m, p) (__SUNPRO_C > 0 && __SUNPRO_C >= 0x ## M ## m ## p) #if defined __has_builtin #define dns_has_builtin(x) __has_builtin(x) #else #define dns_has_builtin(x) 0 #endif #if defined __has_extension #define dns_has_extension(x) __has_extension(x) #else #define dns_has_extension(x) 0 #endif #ifndef HAVE___ASSUME #define HAVE___ASSUME DNS_MSC_PREREQ(8,0,0) #endif #ifndef HAVE___BUILTIN_TYPES_COMPATIBLE_P #define HAVE___BUILTIN_TYPES_COMPATIBLE_P (DNS_GNUC_PREREQ(3,1,1) || __clang__) #endif #ifndef HAVE___BUILTIN_UNREACHABLE #define HAVE___BUILTIN_UNREACHABLE (DNS_GNUC_PREREQ(4,5,0) || dns_has_builtin(__builtin_unreachable)) #endif #ifndef HAVE_PRAGMA_MESSAGE #define HAVE_PRAGMA_MESSAGE (DNS_GNUC_PREREQ(4,4,0) || __clang__ || _MSC_VER) #endif /* * C O M P I L E R A N N O T A T I O N S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #if __GNUC__ #define DNS_NOTUSED __attribute__((unused)) #define DNS_NORETURN __attribute__((noreturn)) #else #define DNS_NOTUSED #define DNS_NORETURN #endif #if __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-parameter" #pragma clang diagnostic ignored "-Wmissing-field-initializers" #elif DNS_GNUC_PREREQ(4,6,0) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif /* * S T A N D A R D M A C R O S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #if HAVE___BUILTIN_TYPES_COMPATIBLE_P #define dns_same_type(a, b, def) __builtin_types_compatible_p(__typeof__ (a), __typeof__ (b)) #else #define dns_same_type(a, b, def) (def) #endif #define dns_isarray(a) (!dns_same_type((a), (&(a)[0]), 0)) /* NB: "_" field silences Sun Studio "zero-sized struct/union" error diagnostic */ #define dns_inline_assert(cond) ((void)(sizeof (struct { int:-!(cond); int _; }))) #if HAVE___ASSUME #define dns_assume(cond) __assume(cond) #elif HAVE___BUILTIN_UNREACHABLE #define dns_assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0) #else #define dns_assume(cond) do { (void)(cond); } while (0) #endif #ifndef lengthof #define lengthof(a) (dns_inline_assert(dns_isarray(a)), (sizeof (a) / sizeof (a)[0])) #endif #ifndef endof #define endof(a) (dns_inline_assert(dns_isarray(a)), &(a)[lengthof((a))]) #endif /* * M I S C E L L A N E O U S C O M P A T * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #if _WIN32 || _WIN64 #define PRIuZ "Iu" #else #define PRIuZ "zu" #endif #ifndef DNS_THREAD_SAFE #if (defined _REENTRANT || defined _THREAD_SAFE) && _POSIX_THREADS > 0 #define DNS_THREAD_SAFE 1 #else #define DNS_THREAD_SAFE 0 #endif #endif #ifndef HAVE__STATIC_ASSERT #define HAVE__STATIC_ASSERT \ (dns_has_extension(c_static_assert) || DNS_GNUC_PREREQ(4,6,0) || \ __C11FEATURES__ || __STDC_VERSION__ >= 201112L) #endif #ifndef HAVE_STATIC_ASSERT #if DNS_GNUC_PREREQ(0,0,0) && !DNS_GNUC_PREREQ(4,6,0) #define HAVE_STATIC_ASSERT 0 /* glibc doesn't check GCC version */ #elif defined(static_assert) #define HAVE_STATIC_ASSERT 1 #else #define HAVE_STATIC_ASSERT 0 #endif #endif #if HAVE_STATIC_ASSERT #define dns_static_assert(cond, msg) static_assert(cond, msg) #elif HAVE__STATIC_ASSERT #define dns_static_assert(cond, msg) _Static_assert(cond, msg) #else #define dns_static_assert(cond, msg) extern char DNS_PP_XPASTE(dns_assert_, __LINE__)[sizeof (int[1 - 2*!(cond)])] #endif /* * D E B U G M A C R O S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int *dns_debug_p(void) { static int debug; return &debug; } /* dns_debug_p() */ #if DNS_DEBUG #undef DNS_DEBUG #define DNS_DEBUG dns_debug #define DNS_SAY_(fmt, ...) \ do { if (DNS_DEBUG > 0) fprintf(stderr, fmt "%.1s", __func__, __LINE__, __VA_ARGS__); } while (0) #define DNS_SAY(...) DNS_SAY_("@@ (%s:%d) " __VA_ARGS__, "\n") #define DNS_HAI DNS_SAY("HAI") #define DNS_SHOW_(P, fmt, ...) do { \ if (DNS_DEBUG > 1) { \ fprintf(stderr, "@@ BEGIN * * * * * * * * * * * *\n"); \ fprintf(stderr, "@@ " fmt "%.0s\n", __VA_ARGS__); \ dns_p_dump((P), stderr); \ fprintf(stderr, "@@ END * * * * * * * * * * * * *\n\n"); \ } \ } while (0) #define DNS_SHOW(...) DNS_SHOW_(__VA_ARGS__, "") #else /* !DNS_DEBUG */ #undef DNS_DEBUG #define DNS_DEBUG 0 #define DNS_SAY(...) #define DNS_HAI #define DNS_SHOW(...) #endif /* DNS_DEBUG */ #define DNS_CARP(...) DNS_SAY(__VA_ARGS__) /* * V E R S I O N R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ const char *dns_vendor(void) { return DNS_VENDOR; } /* dns_vendor() */ int dns_v_rel(void) { return DNS_V_REL; } /* dns_v_rel() */ int dns_v_abi(void) { return DNS_V_ABI; } /* dns_v_abi() */ int dns_v_api(void) { return DNS_V_API; } /* dns_v_api() */ /* * E R R O R R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef EPROTO # define EPROTO EPROTONOSUPPORT #endif #if _WIN32 #define DNS_EINTR WSAEINTR #define DNS_EINPROGRESS WSAEINPROGRESS #define DNS_EISCONN WSAEISCONN #define DNS_EWOULDBLOCK WSAEWOULDBLOCK #define DNS_EALREADY WSAEALREADY #define DNS_EAGAIN EAGAIN #define DNS_ETIMEDOUT WSAETIMEDOUT #define dns_syerr() ((int)GetLastError()) #define dns_soerr() ((int)WSAGetLastError()) #else #define DNS_EINTR EINTR #define DNS_EINPROGRESS EINPROGRESS #define DNS_EISCONN EISCONN #define DNS_EWOULDBLOCK EWOULDBLOCK #define DNS_EALREADY EALREADY #define DNS_EAGAIN EAGAIN #define DNS_ETIMEDOUT ETIMEDOUT #define dns_syerr() errno #define dns_soerr() errno #endif const char *dns_strerror(int error) { switch (error) { case DNS_ENOBUFS: return "DNS packet buffer too small"; case DNS_EILLEGAL: return "Illegal DNS RR name or data"; case DNS_EORDER: return "Attempt to push RR out of section order"; case DNS_ESECTION: return "Invalid section specified"; case DNS_EUNKNOWN: return "Unknown DNS error"; case DNS_EADDRESS: return "Invalid textual address form"; case DNS_ENOQUERY: return "Bad execution state (missing query packet)"; case DNS_ENOANSWER: return "Bad execution state (missing answer packet)"; case DNS_EFETCHED: return "Answer already fetched"; case DNS_ESERVICE: return "The service passed was not recognized for the specified socket type"; case DNS_ENONAME: return "The name does not resolve for the supplied parameters"; case DNS_EFAIL: return "A non-recoverable error occurred when attempting to resolve the name"; case DNS_ECONNFIN: return "Connection closed"; case DNS_EVERIFY: return "Reply failed verification"; default: return strerror(error); } /* switch() */ } /* dns_strerror() */ /* * A T O M I C R O U T I N E S * * Use GCC's __atomic built-ins if possible. Unlike the __sync built-ins, we * can use the preprocessor to detect API and, more importantly, ISA * support. We want to avoid linking headaches where the API depends on an * external library if the ISA (e.g. i386) doesn't support lockless * operation. * * TODO: Support C11's atomic API. Although that may require some finesse * with how we define some public types, such as dns_atomic_t and struct * dns_resolv_conf. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HAVE___ATOMIC_FETCH_ADD #ifdef __ATOMIC_RELAXED #define HAVE___ATOMIC_FETCH_ADD 1 #else #define HAVE___ATOMIC_FETCH_ADD 0 #endif #endif #ifndef HAVE___ATOMIC_FETCH_SUB #define HAVE___ATOMIC_FETCH_SUB HAVE___ATOMIC_FETCH_ADD #endif #ifndef DNS_ATOMIC_FETCH_ADD #if HAVE___ATOMIC_FETCH_ADD && __GCC_ATOMIC_LONG_LOCK_FREE == 2 #define DNS_ATOMIC_FETCH_ADD(i) __atomic_fetch_add((i), 1, __ATOMIC_RELAXED) #else #pragma message("no atomic_fetch_add available") #define DNS_ATOMIC_FETCH_ADD(i) ((*(i))++) #endif #endif #ifndef DNS_ATOMIC_FETCH_SUB #if HAVE___ATOMIC_FETCH_SUB && __GCC_ATOMIC_LONG_LOCK_FREE == 2 #define DNS_ATOMIC_FETCH_SUB(i) __atomic_fetch_sub((i), 1, __ATOMIC_RELAXED) #else #pragma message("no atomic_fetch_sub available") #define DNS_ATOMIC_FETCH_SUB(i) ((*(i))--) #endif #endif static inline unsigned dns_atomic_fetch_add(dns_atomic_t *i) { return DNS_ATOMIC_FETCH_ADD(i); } /* dns_atomic_fetch_add() */ static inline unsigned dns_atomic_fetch_sub(dns_atomic_t *i) { return DNS_ATOMIC_FETCH_SUB(i); } /* dns_atomic_fetch_sub() */ /* * C R Y P T O R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * P R N G */ #ifndef DNS_RANDOM #if defined(HAVE_ARC4RANDOM) \ || defined(__OpenBSD__) \ || defined(__FreeBSD__) \ || defined(__NetBSD__) \ || defined(__APPLE__) #define DNS_RANDOM arc4random #elif __linux #define DNS_RANDOM random #else #define DNS_RANDOM rand #endif #endif #define DNS_RANDOM_arc4random 1 #define DNS_RANDOM_random 2 #define DNS_RANDOM_rand 3 #define DNS_RANDOM_RAND_bytes 4 #define DNS_RANDOM_OPENSSL (DNS_RANDOM_RAND_bytes == DNS_PP_XPASTE(DNS_RANDOM_, DNS_RANDOM)) #if DNS_RANDOM_OPENSSL #include #endif static unsigned dns_random_(void) { #if DNS_RANDOM_OPENSSL unsigned r; _Bool ok; ok = (1 == RAND_bytes((unsigned char *)&r, sizeof r)); assert(ok && "1 == RAND_bytes()"); return r; #else return DNS_RANDOM(); #endif } /* dns_random_() */ dns_random_f **dns_random_p(void) { static dns_random_f *random_f = &dns_random_; return &random_f; } /* dns_random_p() */ /* * P E R M U T A T I O N G E N E R A T O R */ #define DNS_K_TEA_KEY_SIZE 16 #define DNS_K_TEA_BLOCK_SIZE 8 #define DNS_K_TEA_CYCLES 32 #define DNS_K_TEA_MAGIC 0x9E3779B9U struct dns_k_tea { uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)]; unsigned cycles; }; /* struct dns_k_tea */ static void dns_k_tea_init(struct dns_k_tea *tea, uint32_t key[], unsigned cycles) { memcpy(tea->key, key, sizeof tea->key); tea->cycles = (cycles)? cycles : DNS_K_TEA_CYCLES; } /* dns_k_tea_init() */ static void dns_k_tea_encrypt(struct dns_k_tea *tea, uint32_t v[], uint32_t *w) { uint32_t y, z, sum, n; y = v[0]; z = v[1]; sum = 0; for (n = 0; n < tea->cycles; n++) { sum += DNS_K_TEA_MAGIC; y += ((z << 4) + tea->key[0]) ^ (z + sum) ^ ((z >> 5) + tea->key[1]); z += ((y << 4) + tea->key[2]) ^ (y + sum) ^ ((y >> 5) + tea->key[3]); } w[0] = y; w[1] = z; return /* void */; } /* dns_k_tea_encrypt() */ /* * Permutation generator, based on a Luby-Rackoff Feistel construction. * * Specifically, this is a generic balanced Feistel block cipher using TEA * (another block cipher) as the pseudo-random function, F. At best it's as * strong as F (TEA), notwithstanding the seeding. F could be AES, SHA-1, or * perhaps Bernstein's Salsa20 core; I am naively trying to keep things * simple. * * The generator can create a permutation of any set of numbers, as long as * the size of the set is an even power of 2. This limitation arises either * out of an inherent property of balanced Feistel constructions, or by my * own ignorance. I'll tackle an unbalanced construction after I wrap my * head around Schneier and Kelsey's paper. * * CAVEAT EMPTOR. IANAC. */ #define DNS_K_PERMUTOR_ROUNDS 8 struct dns_k_permutor { unsigned stepi, length, limit; unsigned shift, mask, rounds; struct dns_k_tea tea; }; /* struct dns_k_permutor */ static inline unsigned dns_k_permutor_powof(unsigned n) { unsigned m, i = 0; for (m = 1; m < n; m <<= 1, i++) ;; return i; } /* dns_k_permutor_powof() */ static void dns_k_permutor_init(struct dns_k_permutor *p, unsigned low, unsigned high) { uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)]; unsigned width, i; p->stepi = 0; p->length = (high - low) + 1; p->limit = high; width = dns_k_permutor_powof(p->length); width += width % 2; p->shift = width / 2; p->mask = (1U << p->shift) - 1; p->rounds = DNS_K_PERMUTOR_ROUNDS; for (i = 0; i < lengthof(key); i++) key[i] = dns_random(); dns_k_tea_init(&p->tea, key, 0); return /* void */; } /* dns_k_permutor_init() */ static unsigned dns_k_permutor_F(struct dns_k_permutor *p, unsigned k, unsigned x) { uint32_t in[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)], out[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)]; memset(in, '\0', sizeof in); in[0] = k; in[1] = x; dns_k_tea_encrypt(&p->tea, in, out); return p->mask & out[0]; } /* dns_k_permutor_F() */ static unsigned dns_k_permutor_E(struct dns_k_permutor *p, unsigned n) { unsigned l[2], r[2]; unsigned i; i = 0; l[i] = p->mask & (n >> p->shift); r[i] = p->mask & (n >> 0); do { l[(i + 1) % 2] = r[i % 2]; r[(i + 1) % 2] = l[i % 2] ^ dns_k_permutor_F(p, i, r[i % 2]); i++; } while (i < p->rounds - 1); return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0); } /* dns_k_permutor_E() */ DNS_NOTUSED static unsigned dns_k_permutor_D(struct dns_k_permutor *p, unsigned n) { unsigned l[2], r[2]; unsigned i; i = p->rounds - 1; l[i % 2] = p->mask & (n >> p->shift); r[i % 2] = p->mask & (n >> 0); do { i--; r[i % 2] = l[(i + 1) % 2]; l[i % 2] = r[(i + 1) % 2] ^ dns_k_permutor_F(p, i, l[(i + 1) % 2]); } while (i > 0); return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0); } /* dns_k_permutor_D() */ static unsigned dns_k_permutor_step(struct dns_k_permutor *p) { unsigned n; do { n = dns_k_permutor_E(p, p->stepi++); } while (n >= p->length); return n + (p->limit + 1 - p->length); } /* dns_k_permutor_step() */ /* * Simple permutation box. Useful for shuffling rrsets from an iterator. * Uses AES s-box to provide good diffusion. * * Seems to pass muster under runs test. * * $ for i in 0 1 2 3 4 5 6 7 8 9; do ./dns shuffle-16 > /tmp/out; done * $ R -q -f /dev/stdin 2>/dev/null <<-EOF | awk '/p-value/{ print $8 }' * library(lawstat) * runs.test(scan(file="/tmp/out")) * EOF */ static unsigned short dns_k_shuffle16(unsigned short n, unsigned s) { static const unsigned char sbox[256] = { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; unsigned char a, b; unsigned i; a = 0xff & (n >> 0); b = 0xff & (n >> 8); for (i = 0; i < 4; i++) { a ^= 0xff & s; a = sbox[a] ^ b; b = sbox[b] ^ a; s >>= 8; } return ((0xff00 & (a << 8)) | (0x00ff & (b << 0))); } /* dns_k_shuffle16() */ /* * S T A T E M A C H I N E R O U T I N E S * * Application code should define DNS_SM_RESTORE and DNS_SM_SAVE, and the * local variable pc. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_SM_ENTER \ do { \ static const int pc0 = __LINE__; \ DNS_SM_RESTORE; \ switch (pc0 + pc) { \ case __LINE__: (void)0 #define DNS_SM_SAVE_AND_DO(do_statement) \ do { \ pc = __LINE__ - pc0; \ DNS_SM_SAVE; \ do_statement; \ case __LINE__: (void)0; \ } while (0) #define DNS_SM_YIELD(rv) \ DNS_SM_SAVE_AND_DO(return (rv)) #define DNS_SM_EXIT \ do { goto leave; } while (0) #define DNS_SM_LEAVE \ leave: (void)0; \ DNS_SM_SAVE_AND_DO(break); \ } \ } while (0) /* * U T I L I T Y R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_MAXINTERVAL 300 struct dns_clock { time_t sample, elapsed; }; /* struct dns_clock */ static void dns_begin(struct dns_clock *clk) { clk->sample = time(0); clk->elapsed = 0; } /* dns_begin() */ static time_t dns_elapsed(struct dns_clock *clk) { time_t curtime; if ((time_t)-1 == time(&curtime)) return clk->elapsed; if (curtime > clk->sample) clk->elapsed += (time_t)DNS_PP_MIN(difftime(curtime, clk->sample), DNS_MAXINTERVAL); clk->sample = curtime; return clk->elapsed; } /* dns_elapsed() */ DNS_NOTUSED static size_t dns_strnlen(const char *src, size_t m) { size_t n = 0; while (*src++ && n < m) ++n; return n; } /* dns_strnlen() */ DNS_NOTUSED static size_t dns_strnlcpy(char *dst, size_t lim, const char *src, size_t max) { size_t len = dns_strnlen(src, max), n; if (lim > 0) { n = DNS_PP_MIN(lim - 1, len); memcpy(dst, src, n); dst[n] = '\0'; } return len; } /* dns_strnlcpy() */ #if (defined AF_UNIX && !defined _WIN32) #define DNS_HAVE_SOCKADDR_UN 1 #else #define DNS_HAVE_SOCKADDR_UN 0 #endif static size_t dns_af_len(int af) { static const size_t table[AF_MAX] = { [AF_INET6] = sizeof (struct sockaddr_in6), [AF_INET] = sizeof (struct sockaddr_in), #if DNS_HAVE_SOCKADDR_UN [AF_UNIX] = sizeof (struct sockaddr_un), #endif }; return table[af]; } /* dns_af_len() */ #define dns_sa_family(sa) (((struct sockaddr *)(sa))->sa_family) #define dns_sa_len(sa) dns_af_len(dns_sa_family(sa)) #define DNS_SA_NOPORT &dns_sa_noport static unsigned short dns_sa_noport; static unsigned short *dns_sa_port(int af, void *sa) { switch (af) { case AF_INET6: return &((struct sockaddr_in6 *)sa)->sin6_port; case AF_INET: return &((struct sockaddr_in *)sa)->sin_port; default: return DNS_SA_NOPORT; } } /* dns_sa_port() */ static void *dns_sa_addr(int af, const void *sa, socklen_t *size) { switch (af) { case AF_INET6: { struct in6_addr *in6 = &((struct sockaddr_in6 *)sa)->sin6_addr; if (size) *size = sizeof *in6; return in6; } case AF_INET: { struct in_addr *in = &((struct sockaddr_in *)sa)->sin_addr; if (size) *size = sizeof *in; return in; } default: if (size) *size = 0; return 0; } } /* dns_sa_addr() */ #if DNS_HAVE_SOCKADDR_UN #define DNS_SUNPATHMAX (sizeof ((struct sockaddr_un *)0)->sun_path) #endif DNS_NOTUSED static void *dns_sa_path(void *sa, socklen_t *size) { switch (dns_sa_family(sa)) { #if DNS_HAVE_SOCKADDR_UN case AF_UNIX: { char *path = ((struct sockaddr_un *)sa)->sun_path; if (size) *size = dns_strnlen(path, DNS_SUNPATHMAX); return path; } #endif default: if (size) *size = 0; return NULL; } } /* dns_sa_path() */ static int dns_sa_cmp(void *a, void *b) { int cmp, af; if ((cmp = dns_sa_family(a) - dns_sa_family(b))) return cmp; switch ((af = dns_sa_family(a))) { case AF_INET: { struct in_addr *a4, *b4; if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b)))) return cmp; a4 = dns_sa_addr(af, a, NULL); b4 = dns_sa_addr(af, b, NULL); if (ntohl(a4->s_addr) < ntohl(b4->s_addr)) return -1; if (ntohl(a4->s_addr) > ntohl(b4->s_addr)) return 1; return 0; } case AF_INET6: { struct in6_addr *a6, *b6; size_t i; if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b)))) return cmp; a6 = dns_sa_addr(af, a, NULL); b6 = dns_sa_addr(af, b, NULL); /* XXX: do we need to use in6_clearscope()? */ for (i = 0; i < sizeof a6->s6_addr; i++) { if ((cmp = a6->s6_addr[i] - b6->s6_addr[i])) return cmp; } return 0; } #if DNS_HAVE_SOCKADDR_UN case AF_UNIX: { char a_path[DNS_SUNPATHMAX + 1], b_path[sizeof a_path]; dns_strnlcpy(a_path, sizeof a_path, dns_sa_path(a, NULL), DNS_SUNPATHMAX); dns_strnlcpy(b_path, sizeof b_path, dns_sa_path(b, NULL), DNS_SUNPATHMAX); return strcmp(a_path, b_path); } #endif default: return -1; } } /* dns_sa_cmp() */ #if _WIN32 static int dns_inet_pton(int af, const void *src, void *dst) { union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u; int size_of_u = (int)sizeof u; u.sin.sin_family = af; if (0 != WSAStringToAddressA((void *)src, af, (void *)0, (struct sockaddr *)&u, &size_of_u)) return -1; switch (af) { case AF_INET6: *(struct in6_addr *)dst = u.sin6.sin6_addr; return 1; case AF_INET: *(struct in_addr *)dst = u.sin.sin_addr; return 1; default: return 0; } } /* dns_inet_pton() */ static const char *dns_inet_ntop(int af, const void *src, void *dst, unsigned long lim) { union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u; /* NOTE: WSAAddressToString will print .sin_port unless zeroed. */ memset(&u, 0, sizeof u); u.sin.sin_family = af; switch (af) { case AF_INET6: u.sin6.sin6_addr = *(struct in6_addr *)src; break; case AF_INET: u.sin.sin_addr = *(struct in_addr *)src; break; default: return 0; } if (0 != WSAAddressToStringA((struct sockaddr *)&u, dns_sa_len(&u), (void *)0, dst, &lim)) return 0; return dst; } /* dns_inet_ntop() */ #else #define dns_inet_pton(...) inet_pton(__VA_ARGS__) #define dns_inet_ntop(...) inet_ntop(__VA_ARGS__) #endif static dns_error_t dns_pton(int af, const void *src, void *dst) { switch (dns_inet_pton(af, src, dst)) { case 1: return 0; case -1: return dns_soerr(); default: return DNS_EADDRESS; } } /* dns_pton() */ static dns_error_t dns_ntop(int af, const void *src, void *dst, unsigned long lim) { return (dns_inet_ntop(af, src, dst, lim))? 0 : dns_soerr(); } /* dns_ntop() */ size_t dns_strlcpy(char *dst, const char *src, size_t lim) { char *d = dst; char *e = &dst[lim]; const char *s = src; if (d < e) { do { if ('\0' == (*d++ = *s++)) return s - src - 1; } while (d < e); d[-1] = '\0'; } while (*s++ != '\0') ;; return s - src - 1; } /* dns_strlcpy() */ size_t dns_strlcat(char *dst, const char *src, size_t lim) { char *d = memchr(dst, '\0', lim); char *e = &dst[lim]; const char *s = src; const char *p; if (d && d < e) { do { if ('\0' == (*d++ = *s++)) return d - dst - 1; } while (d < e); d[-1] = '\0'; } p = s; while (*s++ != '\0') ;; return lim + (s - p - 1); } /* dns_strlcat() */ static void *dns_reallocarray(void *p, size_t nmemb, size_t size, dns_error_t *error) { void *rp; if (nmemb > 0 && SIZE_MAX / nmemb < size) { *error = EOVERFLOW; return NULL; } if (!(rp = realloc(p, nmemb * size))) *error = (errno)? errno : EINVAL; return rp; } /* dns_reallocarray() */ #if _WIN32 static char *dns_strsep(char **sp, const char *delim) { char *p; if (!(p = *sp)) return 0; *sp += strcspn(p, delim); if (**sp != '\0') { **sp = '\0'; ++*sp; } else *sp = NULL; return p; } /* dns_strsep() */ #else #define dns_strsep(...) strsep(__VA_ARGS__) #endif #if _WIN32 #define strcasecmp(...) _stricmp(__VA_ARGS__) #define strncasecmp(...) _strnicmp(__VA_ARGS__) #endif static inline _Bool dns_isalpha(unsigned char c) { return isalpha(c); } /* dns_isalpha() */ static inline _Bool dns_isdigit(unsigned char c) { return isdigit(c); } /* dns_isdigit() */ static inline _Bool dns_isalnum(unsigned char c) { return isalnum(c); } /* dns_isalnum() */ static inline _Bool dns_isspace(unsigned char c) { return isspace(c); } /* dns_isspace() */ static inline _Bool dns_isgraph(unsigned char c) { return isgraph(c); } /* dns_isgraph() */ static int dns_poll(int fd, short events, int timeout) { fd_set rset, wset; struct timeval tv = { timeout, 0 }; if (!events) return 0; if (fd < 0 || (unsigned)fd >= FD_SETSIZE) return EINVAL; FD_ZERO(&rset); FD_ZERO(&wset); if (events & DNS_POLLIN) FD_SET(fd, &rset); if (events & DNS_POLLOUT) FD_SET(fd, &wset); select(fd + 1, &rset, &wset, 0, (timeout >= 0)? &tv : NULL); return 0; } /* dns_poll() */ #if !_WIN32 DNS_NOTUSED static int dns_sigmask(int how, const sigset_t *set, sigset_t *oset) { #if DNS_THREAD_SAFE return pthread_sigmask(how, set, oset); #else return (0 == sigprocmask(how, set, oset))? 0 : errno; #endif } /* dns_sigmask() */ #endif static size_t dns_send(int fd, const void *src, size_t len, int flags, dns_error_t *error) { long n = send(fd, src, len, flags); if (n < 0) { *error = dns_soerr(); return 0; } else { *error = 0; return n; } } /* dns_send() */ static size_t dns_recv(int fd, void *dst, size_t lim, int flags, dns_error_t *error) { long n = recv(fd, dst, lim, flags); if (n < 0) { *error = dns_soerr(); return 0; } else if (n == 0) { *error = (lim > 0)? DNS_ECONNFIN : EINVAL; return 0; } else { *error = 0; return n; } } /* dns_recv() */ static size_t dns_send_nopipe(int fd, const void *src, size_t len, int flags, dns_error_t *_error) { #if _WIN32 || !defined SIGPIPE || defined SO_NOSIGPIPE return dns_send(fd, src, len, flags, _error); #elif defined MSG_NOSIGNAL return dns_send(fd, src, len, (flags|MSG_NOSIGNAL), _error); #elif _POSIX_REALTIME_SIGNALS > 0 /* require sigtimedwait */ /* * SIGPIPE handling similar to the approach described in * http://krokisplace.blogspot.com/2010/02/suppressing-sigpipe-in-library.html */ sigset_t pending, blocked, piped; size_t count; int error; sigemptyset(&pending); sigpending(&pending); if (!sigismember(&pending, SIGPIPE)) { sigemptyset(&piped); sigaddset(&piped, SIGPIPE); sigemptyset(&blocked); if ((error = dns_sigmask(SIG_BLOCK, &piped, &blocked))) goto error; } count = dns_send(fd, src, len, flags, &error); if (!sigismember(&pending, SIGPIPE)) { int saved = error; const struct timespec ts = { 0, 0 }; if (!count && error == EPIPE) { while (-1 == sigtimedwait(&piped, NULL, &ts) && errno == EINTR) ;; } if ((error = dns_sigmask(SIG_SETMASK, &blocked, NULL))) goto error; error = saved; } *_error = error; return count; error: *_error = error; return 0; #else #error "unable to suppress SIGPIPE" return dns_send(fd, src, len, flags, _error); #endif } /* dns_send_nopipe() */ static dns_error_t dns_connect(int fd, const struct sockaddr *addr, socklen_t addrlen) { if (0 != connect(fd, addr, addrlen)) return dns_soerr(); return 0; } /* dns_connect() */ #define DNS_FOPEN_STDFLAGS "rwabt+" static dns_error_t dns_fopen_addflag(char *dst, const char *src, size_t lim, int fc) { char *p = dst, *pe = dst + lim; /* copy standard flags */ while (*src && strchr(DNS_FOPEN_STDFLAGS, *src)) { if (!(p < pe)) return ENOMEM; *p++ = *src++; } /* append flag to standard flags */ if (!(p < pe)) return ENOMEM; *p++ = fc; /* copy remaining mode string, including '\0' */ do { if (!(p < pe)) return ENOMEM; } while ((*p++ = *src++)); return 0; } /* dns_fopen_addflag() */ static FILE *dns_fopen(const char *path, const char *mode, dns_error_t *_error) { FILE *fp; char mode_cloexec[32]; int error; assert(path && mode && *mode); if (!*path) { error = EINVAL; goto error; } #if _WIN32 || _WIN64 if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'N'))) goto error; if (!(fp = fopen(path, mode_cloexec))) goto syerr; #else if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'e'))) goto error; if (!(fp = fopen(path, mode_cloexec))) { if (errno != EINVAL) goto syerr; if (!(fp = fopen(path, mode))) goto syerr; } #endif return fp; syerr: error = dns_syerr(); error: *_error = error; return NULL; } /* dns_fopen() */ struct dns_hxd_lines_i { int pc; size_t p; }; #define DNS_SM_RESTORE \ do { \ pc = state->pc; \ sp = src + state->p; \ se = src + len; \ } while (0) #define DNS_SM_SAVE \ do { \ state->p = sp - src; \ state->pc = pc; \ } while (0) static size_t dns_hxd_lines(void *dst, size_t lim, const unsigned char *src, size_t len, struct dns_hxd_lines_i *state) { static const unsigned char hex[] = "0123456789abcdef"; static const unsigned char tmpl[] = " | |\n"; unsigned char ln[sizeof tmpl]; const unsigned char *sp, *se; unsigned char *h, *g; unsigned i, n; int pc; DNS_SM_ENTER; while (sp < se) { memcpy(ln, tmpl, sizeof ln); h = &ln[2]; g = &ln[53]; for (n = 0; n < 2; n++) { for (i = 0; i < 8 && se - sp > 0; i++, sp++) { h[0] = hex[0x0f & (*sp >> 4)]; h[1] = hex[0x0f & (*sp >> 0)]; h += 3; *g++ = (dns_isgraph(*sp))? *sp : '.'; } h++; } n = dns_strlcpy(dst, (char *)ln, lim); DNS_SM_YIELD(n); } DNS_SM_EXIT; DNS_SM_LEAVE; return 0; } #undef DNS_SM_SAVE #undef DNS_SM_RESTORE /* * A R I T H M E T I C R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_CHECK_OVERFLOW(error, r, f, ...) \ do { \ uintmax_t _r; \ *(error) = f(&_r, __VA_ARGS__); \ *(r) = _r; \ } while (0) static dns_error_t dns_clamp_overflow(uintmax_t *r, uintmax_t n, uintmax_t clamp) { if (n > clamp) { *r = clamp; return ERANGE; } else { *r = n; return 0; } } /* dns_clamp_overflow() */ static dns_error_t dns_add_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) { if (~a < b) { *r = DNS_PP_MIN(clamp, ~UINTMAX_C(0)); return ERANGE; } else { return dns_clamp_overflow(r, a + b, clamp); } } /* dns_add_overflow() */ static dns_error_t dns_mul_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) { if (a > 0 && UINTMAX_MAX / a < b) { *r = DNS_PP_MIN(clamp, ~UINTMAX_C(0)); return ERANGE; } else { return dns_clamp_overflow(r, a * b, clamp); } } /* dns_mul_overflow() */ /* * F I X E D - S I Z E D B U F F E R R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_B_INIT(src, n) { \ (unsigned char *)(src), \ (unsigned char *)(src), \ (unsigned char *)(src) + (n), \ } #define DNS_B_FROM(src, n) DNS_B_INIT((src), (n)) #define DNS_B_INTO(src, n) DNS_B_INIT((src), (n)) struct dns_buf { const unsigned char *base; unsigned char *p; const unsigned char *pe; dns_error_t error; size_t overflow; }; /* struct dns_buf */ static inline size_t dns_b_tell(struct dns_buf *b) { return b->p - b->base; } static inline dns_error_t dns_b_setoverflow(struct dns_buf *b, size_t n, dns_error_t error) { b->overflow += n; return b->error = error; } DNS_NOTUSED static struct dns_buf * dns_b_into(struct dns_buf *b, void *src, size_t n) { *b = (struct dns_buf)DNS_B_INTO(src, n); return b; } static dns_error_t dns_b_putc(struct dns_buf *b, unsigned char uc) { if (!(b->p < b->pe)) return dns_b_setoverflow(b, 1, DNS_ENOBUFS); *b->p++ = uc; return 0; } static dns_error_t dns_b_pputc(struct dns_buf *b, unsigned char uc, size_t p) { size_t pe = b->pe - b->base; if (pe <= p) return dns_b_setoverflow(b, p - pe + 1, DNS_ENOBUFS); *((unsigned char *)b->base + p) = uc; return 0; } static inline dns_error_t dns_b_put16(struct dns_buf *b, uint16_t u) { return dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0); } static inline dns_error_t dns_b_pput16(struct dns_buf *b, uint16_t u, size_t p) { if (dns_b_pputc(b, u >> 8, p) || dns_b_pputc(b, u >> 0, p + 1)) return b->error; return 0; } DNS_NOTUSED static inline dns_error_t dns_b_put32(struct dns_buf *b, uint32_t u) { return dns_b_putc(b, u >> 24), dns_b_putc(b, u >> 16), dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0); } static dns_error_t dns_b_put(struct dns_buf *b, const void *src, size_t len) { size_t n = DNS_PP_MIN((size_t)(b->pe - b->p), len); memcpy(b->p, src, n); b->p += n; if (n < len) return dns_b_setoverflow(b, len - n, DNS_ENOBUFS); return 0; } static dns_error_t dns_b_puts(struct dns_buf *b, const void *src) { return dns_b_put(b, src, strlen(src)); } DNS_NOTUSED static inline dns_error_t dns_b_fmtju(struct dns_buf *b, const uintmax_t u, const unsigned width) { size_t digits, padding, overflow; uintmax_t r; unsigned char *tp, *te, tc; digits = 0; r = u; do { digits++; r /= 10; } while (r); padding = width - DNS_PP_MIN(digits, width); overflow = (digits + padding) - DNS_PP_MIN((size_t)(b->pe - b->p), (digits + padding)); while (padding--) { dns_b_putc(b, '0'); } digits = 0; tp = b->p; r = u; do { if (overflow < ++digits) dns_b_putc(b, '0' + (r % 10)); r /= 10; } while (r); te = b->p; while (tp < te) { tc = *--te; *te = *tp; *tp++ = tc; } return b->error; } static void dns_b_popc(struct dns_buf *b) { if (b->overflow && !--b->overflow) b->error = 0; if (b->p > b->base) b->p--; } static inline const char * dns_b_tolstring(struct dns_buf *b, size_t *n) { if (b->p < b->pe) { *b->p = '\0'; *n = b->p - b->base; return (const char *)b->base; } else if (b->p > b->base) { if (b->p[-1] != '\0') { dns_b_setoverflow(b, 1, DNS_ENOBUFS); b->p[-1] = '\0'; } *n = &b->p[-1] - b->base; return (const char *)b->base; } else { *n = 0; return ""; } } static inline const char * dns_b_tostring(struct dns_buf *b) { size_t n; return dns_b_tolstring(b, &n); } static inline size_t dns_b_strlen(struct dns_buf *b) { size_t n; dns_b_tolstring(b, &n); return n; } static inline size_t dns_b_strllen(struct dns_buf *b) { size_t n = dns_b_strlen(b); return n + b->overflow; } DNS_NOTUSED static const struct dns_buf * dns_b_from(const struct dns_buf *b, const void *src, size_t n) { *(struct dns_buf *)b = (struct dns_buf)DNS_B_FROM(src, n); return b; } static inline int dns_b_getc(const struct dns_buf *_b, const int eof) { struct dns_buf *b = (struct dns_buf *)_b; if (!(b->p < b->pe)) return dns_b_setoverflow(b, 1, DNS_EILLEGAL), eof; return *b->p++; } static inline intmax_t dns_b_get16(const struct dns_buf *b, const intmax_t eof) { intmax_t n; n = (dns_b_getc(b, 0) << 8); n |= (dns_b_getc(b, 0) << 0); return (!b->overflow)? n : eof; } DNS_NOTUSED static inline intmax_t dns_b_get32(const struct dns_buf *b, const intmax_t eof) { intmax_t n; n = (dns_b_get16(b, 0) << 16); n |= (dns_b_get16(b, 0) << 0); return (!b->overflow)? n : eof; } static inline dns_error_t dns_b_move(struct dns_buf *dst, const struct dns_buf *_src, size_t n) { struct dns_buf *src = (struct dns_buf *)_src; size_t src_n = DNS_PP_MIN((size_t)(src->pe - src->p), n); size_t src_r = n - src_n; dns_b_put(dst, src->p, src_n); src->p += src_n; if (src_r) return dns_b_setoverflow(src, src_r, DNS_EILLEGAL); return dst->error; } /* * T I M E R O U T I N E S * * Most functions still rely on the older time routines defined in the * utility routines section, above. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_TIME_C(n) UINT64_C(n) #define DNS_TIME_INF (~DNS_TIME_C(0)) typedef uint64_t dns_time_t; typedef dns_time_t dns_microseconds_t; static dns_error_t dns_time_add(dns_time_t *r, dns_time_t a, dns_time_t b) { int error; DNS_CHECK_OVERFLOW(&error, r, dns_add_overflow, a, b, DNS_TIME_INF); return error; } static dns_error_t dns_time_mul(dns_time_t *r, dns_time_t a, dns_time_t b) { int error; DNS_CHECK_OVERFLOW(&error, r, dns_mul_overflow, a, b, DNS_TIME_INF); return error; } static dns_error_t dns_time_diff(dns_time_t *r, dns_time_t a, dns_time_t b) { if (a < b) { *r = DNS_TIME_C(0); return ERANGE; } else { *r = a - b; return 0; } } static dns_microseconds_t dns_ts2us(const struct timespec *ts, _Bool rup) { if (ts) { dns_time_t sec = DNS_PP_MAX(0, ts->tv_sec); dns_time_t nsec = DNS_PP_MAX(0, ts->tv_nsec); dns_time_t usec = nsec / 1000; dns_microseconds_t r; if (rup && nsec % 1000 > 0) usec++; dns_time_mul(&r, sec, DNS_TIME_C(1000000)); dns_time_add(&r, r, usec); return r; } else { return DNS_TIME_INF; } } /* dns_ts2us() */ static struct timespec *dns_tv2ts(struct timespec *ts, const struct timeval *tv) { if (tv) { ts->tv_sec = tv->tv_sec; ts->tv_nsec = tv->tv_usec * 1000; return ts; } else { return NULL; } } /* dns_tv2ts() */ static size_t dns_utime_print(void *_dst, size_t lim, dns_microseconds_t us) { struct dns_buf dst = DNS_B_INTO(_dst, lim); dns_b_fmtju(&dst, us / 1000000, 1); dns_b_putc(&dst, '.'); dns_b_fmtju(&dst, us % 1000000, 6); return dns_b_strllen(&dst); } /* dns_utime_print() */ /* * P A C K E T R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ unsigned dns_p_count(struct dns_packet *P, enum dns_section section) { unsigned count; switch (section) { case DNS_S_QD: return ntohs(dns_header(P)->qdcount); case DNS_S_AN: return ntohs(dns_header(P)->ancount); case DNS_S_NS: return ntohs(dns_header(P)->nscount); case DNS_S_AR: return ntohs(dns_header(P)->arcount); default: count = 0; if (section & DNS_S_QD) count += ntohs(dns_header(P)->qdcount); if (section & DNS_S_AN) count += ntohs(dns_header(P)->ancount); if (section & DNS_S_NS) count += ntohs(dns_header(P)->nscount); if (section & DNS_S_AR) count += ntohs(dns_header(P)->arcount); return count; } } /* dns_p_count() */ struct dns_packet *dns_p_init(struct dns_packet *P, size_t size) { if (!P) return 0; assert(size >= offsetof(struct dns_packet, data) + 12); memset(P, 0, sizeof *P); P->size = size - offsetof(struct dns_packet, data); P->end = 12; memset(P->data, '\0', 12); return P; } /* dns_p_init() */ static struct dns_packet *dns_p_reset(struct dns_packet *P) { return dns_p_init(P, offsetof(struct dns_packet, data) + P->size); } /* dns_p_reset() */ static unsigned short dns_p_qend(struct dns_packet *P) { unsigned short qend = 12; unsigned i, count = dns_p_count(P, DNS_S_QD); for (i = 0; i < count && qend < P->end; i++) { if (P->end == (qend = dns_d_skip(qend, P))) goto invalid; if (P->end - qend < 4) goto invalid; qend += 4; } return DNS_PP_MIN(qend, P->end); invalid: return P->end; } /* dns_p_qend() */ struct dns_packet *dns_p_make(size_t len, int *error) { struct dns_packet *P; size_t size = dns_p_calcsize(len); if (!(P = dns_p_init(malloc(size), size))) *error = dns_syerr(); return P; } /* dns_p_make() */ static void dns_p_free(struct dns_packet *P) { free(P); } /* dns_p_free() */ /* convenience routine to free any existing packet before storing new packet */ static struct dns_packet *dns_p_setptr(struct dns_packet **dst, struct dns_packet *src) { dns_p_free(*dst); *dst = src; return src; } /* dns_p_setptr() */ static struct dns_packet *dns_p_movptr(struct dns_packet **dst, struct dns_packet **src) { dns_p_setptr(dst, *src); *src = NULL; return *dst; } /* dns_p_movptr() */ int dns_p_grow(struct dns_packet **P) { struct dns_packet *tmp; size_t size; int error; if (!*P) { if (!(*P = dns_p_make(DNS_P_QBUFSIZ, &error))) return error; return 0; } size = dns_p_sizeof(*P); size |= size >> 1; size |= size >> 2; size |= size >> 4; size |= size >> 8; size++; if (size > 65536) return DNS_ENOBUFS; if (!(tmp = realloc(*P, dns_p_calcsize(size)))) return dns_syerr(); tmp->size = size; *P = tmp; return 0; } /* dns_p_grow() */ struct dns_packet *dns_p_copy(struct dns_packet *P, const struct dns_packet *P0) { if (!P) return 0; P->end = DNS_PP_MIN(P->size, P0->end); memcpy(P->data, P0->data, P->end); return P; } /* dns_p_copy() */ struct dns_packet *dns_p_merge(struct dns_packet *A, enum dns_section Amask, struct dns_packet *B, enum dns_section Bmask, int *error_) { size_t bufsiz = DNS_PP_MIN(65535, ((A)? A->end : 0) + ((B)? B->end : 0)); struct dns_packet *M; enum dns_section section; struct dns_rr rr, mr; int error, copy; if (!A && B) { A = B; Amask = Bmask; B = 0; } merge: if (!(M = dns_p_make(bufsiz, &error))) goto error; for (section = DNS_S_QD; (DNS_S_ALL & section); section <<= 1) { if (A && (section & Amask)) { dns_rr_foreach(&rr, A, .section = section) { if ((error = dns_rr_copy(M, &rr, A))) goto error; } } if (B && (section & Bmask)) { dns_rr_foreach(&rr, B, .section = section) { copy = 1; dns_rr_foreach(&mr, M, .type = rr.type, .section = DNS_S_ALL) { if (!(copy = dns_rr_cmp(&rr, B, &mr, M))) break; } if (copy && (error = dns_rr_copy(M, &rr, B))) goto error; } } } return M; error: dns_p_setptr(&M, NULL); if (error == DNS_ENOBUFS && bufsiz < 65535) { bufsiz = DNS_PP_MIN(65535, bufsiz * 2); goto merge; } *error_ = error; return 0; } /* dns_p_merge() */ static unsigned short dns_l_skip(unsigned short, const unsigned char *, size_t); void dns_p_dictadd(struct dns_packet *P, unsigned short dn) { unsigned short lp, lptr, i; lp = dn; while (lp < P->end) { if (0xc0 == (0xc0 & P->data[lp]) && P->end - lp >= 2 && lp != dn) { lptr = ((0x3f & P->data[lp + 0]) << 8) | ((0xff & P->data[lp + 1]) << 0); for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) { if (P->dict[i] == lptr) { P->dict[i] = dn; return; } } } lp = dns_l_skip(lp, P->data, P->end); } for (i = 0; i < lengthof(P->dict); i++) { if (!P->dict[i]) { P->dict[i] = dn; break; } } } /* dns_p_dictadd() */ static inline uint16_t plus1_ns (uint16_t count_net) { uint16_t count = ntohs (count_net); count++; return htons (count); } int dns_p_push(struct dns_packet *P, enum dns_section section, const void *dn, size_t dnlen, enum dns_type type, enum dns_class class, unsigned ttl, const void *any) { size_t end = P->end; int error; if ((error = dns_d_push(P, dn, dnlen))) goto error; if (P->size - P->end < 4) goto nobufs; P->data[P->end++] = 0xff & (type >> 8); P->data[P->end++] = 0xff & (type >> 0); P->data[P->end++] = 0xff & (class >> 8); P->data[P->end++] = 0xff & (class >> 0); if (section == DNS_S_QD) goto update; if (P->size - P->end < 6) goto nobufs; if (type != DNS_T_OPT) ttl = DNS_PP_MIN(ttl, 0x7fffffffU); P->data[P->end++] = ttl >> 24; P->data[P->end++] = ttl >> 16; P->data[P->end++] = ttl >> 8; P->data[P->end++] = ttl >> 0; if ((error = dns_any_push(P, (union dns_any *)any, type))) goto error; update: switch (section) { case DNS_S_QD: if (dns_p_count(P, DNS_S_AN|DNS_S_NS|DNS_S_AR)) goto order; if (!P->memo.qd.base && (error = dns_p_study(P))) goto error; dns_header(P)->qdcount = plus1_ns (dns_header(P)->qdcount); P->memo.qd.end = P->end; P->memo.an.base = P->end; P->memo.an.end = P->end; P->memo.ns.base = P->end; P->memo.ns.end = P->end; P->memo.ar.base = P->end; P->memo.ar.end = P->end; break; case DNS_S_AN: if (dns_p_count(P, DNS_S_NS|DNS_S_AR)) goto order; if (!P->memo.an.base && (error = dns_p_study(P))) goto error; dns_header(P)->ancount = plus1_ns (dns_header(P)->ancount); P->memo.an.end = P->end; P->memo.ns.base = P->end; P->memo.ns.end = P->end; P->memo.ar.base = P->end; P->memo.ar.end = P->end; break; case DNS_S_NS: if (dns_p_count(P, DNS_S_AR)) goto order; if (!P->memo.ns.base && (error = dns_p_study(P))) goto error; dns_header(P)->nscount = plus1_ns (dns_header(P)->nscount); P->memo.ns.end = P->end; P->memo.ar.base = P->end; P->memo.ar.end = P->end; break; case DNS_S_AR: if (!P->memo.ar.base && (error = dns_p_study(P))) goto error; dns_header(P)->arcount = plus1_ns (dns_header(P)->arcount); P->memo.ar.end = P->end; if (type == DNS_T_OPT && !P->memo.opt.p) { P->memo.opt.p = end; P->memo.opt.maxudp = class; P->memo.opt.ttl = ttl; } break; default: error = DNS_ESECTION; goto error; } /* switch() */ return 0; nobufs: error = DNS_ENOBUFS; goto error; order: error = DNS_EORDER; goto error; error: P->end = end; return error; } /* dns_p_push() */ #define DNS_SM_RESTORE do { pc = state->pc; error = state->error; } while (0) #define DNS_SM_SAVE do { state->error = error; state->pc = pc; } while (0) struct dns_p_lines_i { int pc; enum dns_section section; struct dns_rr rr; int error; }; static size_t dns_p_lines_fmt(void *dst, size_t lim, dns_error_t *_error, const char *fmt, ...) { va_list ap; int error = 0, n; va_start(ap, fmt); if ((n = vsnprintf(dst, lim, fmt, ap)) < 0) error = errno; va_end(ap); *_error = error; return DNS_PP_MAX(n, 0); } /* dns_p_lines_fmt() */ #define DNS_P_LINE(...) \ do { \ len = dns_p_lines_fmt(dst, lim, &error, __VA_ARGS__); \ if (len == 0 && error) \ goto error; \ DNS_SM_YIELD(len); \ } while (0) static size_t dns_p_lines(void *dst, size_t lim, dns_error_t *_error, struct dns_packet *P, struct dns_rr_i *I, struct dns_p_lines_i *state) { int error, pc; size_t len; char __dst[DNS_STRMAXLEN + 1] = { 0 }; *_error = 0; DNS_SM_ENTER; DNS_P_LINE(";; [HEADER]\n"); DNS_P_LINE(";; qid : %d\n", ntohs(dns_header(P)->qid)); DNS_P_LINE(";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr); DNS_P_LINE(";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode); DNS_P_LINE(";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa); DNS_P_LINE(";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc); DNS_P_LINE(";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd); DNS_P_LINE(";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra); DNS_P_LINE(";; rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P)); while (dns_rr_grep(&state->rr, 1, I, P, &error)) { if (state->section != state->rr.section) { DNS_P_LINE("\n"); DNS_P_LINE(";; [%s:%d]\n", dns_strsection(state->rr.section, __dst), dns_p_count(P, state->rr.section)); } if (!(len = dns_rr_print(dst, lim, &state->rr, P, &error))) goto error; dns_strlcat(dst, "\n", lim); DNS_SM_YIELD(len + 1); state->section = state->rr.section; } if (error) goto error; DNS_SM_EXIT; error: for (;;) { *_error = error; DNS_SM_YIELD(0); } DNS_SM_LEAVE; *_error = 0; return 0; } /* dns_p_lines() */ #undef DNS_P_LINE #undef DNS_SM_SAVE #undef DNS_SM_RESTORE static void dns_p_dump3(struct dns_packet *P, struct dns_rr_i *I, FILE *fp) { struct dns_p_lines_i lines = { 0 }; char line[sizeof (union dns_any) * 2]; size_t len; int error; while ((len = dns_p_lines(line, sizeof line, &error, P, I, &lines))) { if (len < sizeof line) { fwrite(line, 1, len, fp); } else { fwrite(line, 1, sizeof line - 1, fp); fputc('\n', fp); } } } /* dns_p_dump3() */ void dns_p_dump(struct dns_packet *P, FILE *fp) { struct dns_rr_i I_instance = { 0 }; dns_p_dump3(P, &I_instance, fp); } /* dns_p_dump() */ static void dns_s_unstudy(struct dns_s_memo *m) { m->base = 0; m->end = 0; } static void dns_m_unstudy(struct dns_p_memo *m) { dns_s_unstudy(&m->qd); dns_s_unstudy(&m->an); dns_s_unstudy(&m->ns); dns_s_unstudy(&m->ar); m->opt.p = 0; m->opt.maxudp = 0; m->opt.ttl = 0; } /* dns_m_unstudy() */ static int dns_s_study(struct dns_s_memo *m, enum dns_section section, unsigned short base, struct dns_packet *P) { unsigned short count, rp; count = dns_p_count(P, section); for (rp = base; count && rp < P->end; count--) rp = dns_rr_skip(rp, P); m->base = base; m->end = rp; return 0; } /* dns_s_study() */ static int dns_m_study(struct dns_p_memo *m, struct dns_packet *P) { struct dns_rr rr; int error; if ((error = dns_s_study(&m->qd, DNS_S_QD, 12, P))) goto error; if ((error = dns_s_study(&m->an, DNS_S_AN, m->qd.end, P))) goto error; if ((error = dns_s_study(&m->ns, DNS_S_NS, m->an.end, P))) goto error; if ((error = dns_s_study(&m->ar, DNS_S_AR, m->ns.end, P))) goto error; m->opt.p = 0; m->opt.maxudp = 0; m->opt.ttl = 0; dns_rr_foreach(&rr, P, .type = DNS_T_OPT, .section = DNS_S_AR) { m->opt.p = rr.dn.p; m->opt.maxudp = rr.class; m->opt.ttl = rr.ttl; break; } return 0; error: dns_m_unstudy(m); return error; } /* dns_m_study() */ int dns_p_study(struct dns_packet *P) { return dns_m_study(&P->memo, P); } /* dns_p_study() */ enum dns_rcode dns_p_rcode(struct dns_packet *P) { return 0xfff & ((P->memo.opt.ttl >> 20) | dns_header(P)->rcode); } /* dns_p_rcode() */ /* * Q U E R Y P A C K E T R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_Q_RD 0x1 /* recursion desired */ #define DNS_Q_EDNS0 0x2 /* include OPT RR */ static dns_error_t dns_q_make2(struct dns_packet **_Q, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass, int qflags) { struct dns_packet *Q = NULL; int error; if (dns_p_movptr(&Q, _Q)) { dns_p_reset(Q); } else if (!(Q = dns_p_make(DNS_P_QBUFSIZ, &error))) { goto error; } if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, qtype, qclass, 0, 0))) goto error; dns_header(Q)->rd = !!(qflags & DNS_Q_RD); if (qflags & DNS_Q_EDNS0) { struct dns_opt opt = DNS_OPT_INIT(&opt); opt.version = 0; /* RFC 6891 version */ opt.maxudp = 4096; if ((error = dns_p_push(Q, DNS_S_AR, ".", 1, DNS_T_OPT, dns_opt_class(&opt), dns_opt_ttl(&opt), &opt))) goto error; } *_Q = Q; return 0; error: dns_p_free(Q); return error; } static dns_error_t dns_q_make(struct dns_packet **Q, const char *qname, enum dns_type qtype, enum dns_class qclass, int qflags) { return dns_q_make2(Q, qname, strlen(qname), qtype, qclass, qflags); } static dns_error_t dns_q_remake(struct dns_packet **Q, int qflags) { char qname[DNS_D_MAXNAME + 1]; size_t qlen; struct dns_rr rr; int error; assert(Q && *Q); if ((error = dns_rr_parse(&rr, 12, *Q))) return error; if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, *Q, &error))) return error; if (qlen >= sizeof qname) return DNS_EILLEGAL; return dns_q_make2(Q, qname, qlen, rr.type, rr.class, qflags); } /* * D O M A I N N A M E R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef DNS_D_MAXPTRS #define DNS_D_MAXPTRS 127 /* Arbitrary; possible, valid depth is something like packet size / 2 + fudge. */ #endif static size_t dns_l_expand(unsigned char *dst, size_t lim, unsigned short src, unsigned short *nxt, const unsigned char *data, size_t end) { unsigned short len; unsigned nptrs = 0; retry: if (src >= end) goto invalid; switch (0x03 & (data[src] >> 6)) { case 0x00: len = (0x3f & (data[src++])); if (end - src < len) goto invalid; if (lim > 0) { memcpy(dst, &data[src], DNS_PP_MIN(lim, len)); dst[DNS_PP_MIN(lim - 1, len)] = '\0'; } *nxt = src + len; return len; case 0x01: goto invalid; case 0x02: goto invalid; case 0x03: if (++nptrs > DNS_D_MAXPTRS) goto invalid; if (end - src < 2) goto invalid; src = ((0x3f & data[src + 0]) << 8) | ((0xff & data[src + 1]) << 0); goto retry; } /* switch() */ /* NOT REACHED */ invalid: *nxt = end; return 0; } /* dns_l_expand() */ static unsigned short dns_l_skip(unsigned short src, const unsigned char *data, size_t end) { unsigned short len; if (src >= end) goto invalid; switch (0x03 & (data[src] >> 6)) { case 0x00: len = (0x3f & (data[src++])); if (end - src < len) goto invalid; return (len)? src + len : end; case 0x01: goto invalid; case 0x02: goto invalid; case 0x03: return end; } /* switch() */ /* NOT REACHED */ invalid: return end; } /* dns_l_skip() */ static _Bool dns_d_isanchored(const void *_src, size_t len) { const unsigned char *src = _src; return len > 0 && src[len - 1] == '.'; } /* dns_d_isanchored() */ static size_t dns_d_ndots(const void *_src, size_t len) { const unsigned char *p = _src, *pe = p + len; size_t ndots = 0; while ((p = memchr(p, '.', pe - p))) { ndots++; p++; } return ndots; } /* dns_d_ndots() */ static size_t dns_d_trim(void *dst_, size_t lim, const void *src_, size_t len, int flags) { unsigned char *dst = dst_; const unsigned char *src = src_; size_t dp = 0, sp = 0; int lc; /* trim any leading dot(s) */ while (sp < len && src[sp] == '.') sp++; for (lc = 0; sp < len; lc = src[sp++]) { /* trim extra dot(s) */ if (src[sp] == '.' && lc == '.') continue; if (dp < lim) dst[dp] = src[sp]; dp++; } if ((flags & DNS_D_ANCHOR) && lc != '.') { if (dp < lim) dst[dp] = '.'; dp++; } if (lim > 0) dst[DNS_PP_MIN(dp, lim - 1)] = '\0'; return dp; } /* dns_d_trim() */ char *dns_d_init(void *dst, size_t lim, const void *src, size_t len, int flags) { if (flags & DNS_D_TRIM) { dns_d_trim(dst, lim, src, len, flags); } if (flags & DNS_D_ANCHOR) { dns_d_anchor(dst, lim, src, len); } else { memmove(dst, src, DNS_PP_MIN(lim, len)); if (lim > 0) ((char *)dst)[DNS_PP_MIN(len, lim - 1)] = '\0'; } return dst; } /* dns_d_init() */ size_t dns_d_anchor(void *dst, size_t lim, const void *src, size_t len) { if (len == 0) return 0; memmove(dst, src, DNS_PP_MIN(lim, len)); if (((const char *)src)[len - 1] != '.') { if (len < lim) ((char *)dst)[len] = '.'; len++; } if (lim > 0) ((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0'; return len; } /* dns_d_anchor() */ size_t dns_d_cleave(void *dst, size_t lim, const void *src, size_t len) { const char *dot; /* XXX: Skip any leading dot. Handles cleaving root ".". */ if (len == 0 || !(dot = memchr((const char *)src + 1, '.', len - 1))) return 0; len -= dot - (const char *)src; /* XXX: Unless root, skip the label's trailing dot. */ if (len > 1) { src = ++dot; len--; } else src = dot; memmove(dst, src, DNS_PP_MIN(lim, len)); if (lim > 0) ((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0'; return len; } /* dns_d_cleave() */ size_t dns_d_comp(void *dst_, size_t lim, const void *src_, size_t len, struct dns_packet *P, int *error) { struct { unsigned char *b; size_t p, x; } dst, src; unsigned char ch = '.'; dst.b = dst_; dst.p = 0; dst.x = 1; src.b = (unsigned char *)src_; src.p = 0; src.x = 0; while (src.x < len) { ch = src.b[src.x]; if (ch == '.') { if (dst.p < lim) dst.b[dst.p] = (0x3f & (src.x - src.p)); dst.p = dst.x++; src.p = ++src.x; } else { if (dst.x < lim) dst.b[dst.x] = ch; dst.x++; src.x++; } } /* while() */ if (src.x > src.p) { if (dst.p < lim) dst.b[dst.p] = (0x3f & (src.x - src.p)); dst.p = dst.x; } if (dst.p > 1) { if (dst.p < lim) dst.b[dst.p] = 0x00; dst.p++; } #if 1 if (dst.p < lim) { struct { unsigned char label[DNS_D_MAXLABEL + 1]; size_t len; unsigned short p, x, y; } a, b; unsigned i; a.p = 0; while ((a.len = dns_l_expand(a.label, sizeof a.label, a.p, &a.x, dst.b, lim))) { for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) { b.p = P->dict[i]; while ((b.len = dns_l_expand(b.label, sizeof b.label, b.p, &b.x, P->data, P->end))) { a.y = a.x; b.y = b.x; while (a.len && b.len && 0 == strcasecmp((char *)a.label, (char *)b.label)) { a.len = dns_l_expand(a.label, sizeof a.label, a.y, &a.y, dst.b, lim); b.len = dns_l_expand(b.label, sizeof b.label, b.y, &b.y, P->data, P->end); } if (a.len == 0 && b.len == 0 && b.p <= 0x3fff) { dst.b[a.p++] = 0xc0 | (0x3f & (b.p >> 8)); dst.b[a.p++] = (0xff & (b.p >> 0)); /* silence static analyzers */ dns_assume(a.p > 0); return a.p; } b.p = b.x; } /* while() */ } /* for() */ a.p = a.x; } /* while() */ } /* if () */ #endif if (!dst.p) *error = DNS_EILLEGAL; return dst.p; } /* dns_d_comp() */ unsigned short dns_d_skip(unsigned short src, struct dns_packet *P) { unsigned short len; while (src < P->end) { switch (0x03 & (P->data[src] >> 6)) { case 0x00: /* FOLLOWS */ len = (0x3f & P->data[src++]); if (0 == len) { /* success ==> */ return src; } else if (P->end - src > len) { src += len; break; } else goto invalid; /* NOT REACHED */ case 0x01: /* RESERVED */ goto invalid; case 0x02: /* RESERVED */ goto invalid; case 0x03: /* POINTER */ if (P->end - src < 2) goto invalid; src += 2; /* success ==> */ return src; } /* switch() */ } /* while() */ invalid: return P->end; } /* dns_d_skip() */ #include size_t dns_d_expand(void *dst, size_t lim, unsigned short src, struct dns_packet *P, int *error) { size_t dstp = 0; unsigned nptrs = 0; unsigned char len; while (src < P->end) { switch ((0x03 & (P->data[src] >> 6))) { case 0x00: /* FOLLOWS */ len = (0x3f & P->data[src]); if (0 == len) { if (dstp == 0) { if (dstp < lim) ((unsigned char *)dst)[dstp] = '.'; dstp++; } /* NUL terminate */ if (lim > 0) ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0'; /* success ==> */ return dstp; } src++; if (P->end - src < len) goto toolong; if (dstp < lim) memcpy(&((unsigned char *)dst)[dstp], &P->data[src], DNS_PP_MIN(len, lim - dstp)); src += len; dstp += len; if (dstp < lim) ((unsigned char *)dst)[dstp] = '.'; dstp++; nptrs = 0; continue; case 0x01: /* RESERVED */ goto reserved; case 0x02: /* RESERVED */ goto reserved; case 0x03: /* POINTER */ if (++nptrs > DNS_D_MAXPTRS) goto toolong; if (P->end - src < 2) goto toolong; src = ((0x3f & P->data[src + 0]) << 8) | ((0xff & P->data[src + 1]) << 0); continue; } /* switch() */ } /* while() */ toolong: *error = DNS_EILLEGAL; if (lim > 0) ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0'; return 0; reserved: *error = DNS_EILLEGAL; if (lim > 0) ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0'; return 0; } /* dns_d_expand() */ int dns_d_push(struct dns_packet *P, const void *dn, size_t len) { size_t lim = P->size - P->end; unsigned dp = P->end; int error = DNS_EILLEGAL; /* silence compiler */ len = dns_d_comp(&P->data[dp], lim, dn, len, P, &error); if (len == 0) return error; if (len > lim) return DNS_ENOBUFS; P->end += len; dns_p_dictadd(P, dp); return 0; } /* dns_d_push() */ size_t dns_d_cname(void *dst, size_t lim, const void *dn, size_t len, struct dns_packet *P, int *error_) { char host[DNS_D_MAXNAME + 1]; struct dns_rr_i i; struct dns_rr rr; unsigned depth; int error; if (sizeof host <= dns_d_anchor(host, sizeof host, dn, len)) { error = ENAMETOOLONG; goto error; } for (depth = 0; depth < 7; depth++) { memset(&i, 0, sizeof i); i.section = DNS_S_ALL & ~DNS_S_QD; i.name = host; i.type = DNS_T_CNAME; if (!dns_rr_grep(&rr, 1, &i, P, &error)) break; if ((error = dns_cname_parse((struct dns_cname *)host, &rr, P))) goto error; } return dns_strlcpy(dst, host, lim); error: *error_ = error; return 0; } /* dns_d_cname() */ /* * R E S O U R C E R E C O R D R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int dns_rr_copy(struct dns_packet *P, struct dns_rr *rr, struct dns_packet *Q) { unsigned char dn[DNS_D_MAXNAME + 1]; union dns_any any; size_t len; int error; if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, Q, &error))) return error; else if (len >= sizeof dn) return DNS_EILLEGAL; if (rr->section != DNS_S_QD && (error = dns_any_parse(dns_any_init(&any, sizeof any), rr, Q))) return error; return dns_p_push(P, rr->section, dn, len, rr->type, rr->class, rr->ttl, &any); } /* dns_rr_copy() */ int dns_rr_parse(struct dns_rr *rr, unsigned short src, struct dns_packet *P) { unsigned short p = src; if (src >= P->end) goto invalid; rr->dn.p = p; rr->dn.len = (p = dns_d_skip(p, P)) - rr->dn.p; if (P->end - p < 4) goto invalid; rr->type = ((0xff & P->data[p + 0]) << 8) | ((0xff & P->data[p + 1]) << 0); rr->class = ((0xff & P->data[p + 2]) << 8) | ((0xff & P->data[p + 3]) << 0); p += 4; if (src < dns_p_qend(P)) { rr->section = DNS_S_QUESTION; rr->ttl = 0; rr->rd.p = 0; rr->rd.len = 0; return 0; } if (P->end - p < 4) goto invalid; rr->ttl = ((0xff & P->data[p + 0]) << 24) | ((0xff & P->data[p + 1]) << 16) | ((0xff & P->data[p + 2]) << 8) | ((0xff & P->data[p + 3]) << 0); if (rr->type != DNS_T_OPT) rr->ttl = DNS_PP_MIN(rr->ttl, 0x7fffffffU); p += 4; if (P->end - p < 2) goto invalid; rr->rd.len = ((0xff & P->data[p + 0]) << 8) | ((0xff & P->data[p + 1]) << 0); rr->rd.p = p + 2; p += 2; if (P->end - p < rr->rd.len) goto invalid; return 0; invalid: return DNS_EILLEGAL; } /* dns_rr_parse() */ static unsigned short dns_rr_len(const unsigned short src, struct dns_packet *P) { unsigned short rp, rdlen; rp = dns_d_skip(src, P); if (P->end - rp < 4) return P->end - src; rp += 4; /* TYPE, CLASS */ if (rp <= dns_p_qend(P)) return rp - src; if (P->end - rp < 6) return P->end - src; rp += 6; /* TTL, RDLEN */ rdlen = ((0xff & P->data[rp - 2]) << 8) | ((0xff & P->data[rp - 1]) << 0); if (P->end - rp < rdlen) return P->end - src; rp += rdlen; return rp - src; } /* dns_rr_len() */ unsigned short dns_rr_skip(unsigned short src, struct dns_packet *P) { return src + dns_rr_len(src, P); } /* dns_rr_skip() */ static enum dns_section dns_rr_section(unsigned short src, struct dns_packet *P) { enum dns_section section; unsigned count, index; unsigned short rp; if (src >= P->memo.qd.base && src < P->memo.qd.end) return DNS_S_QD; if (src >= P->memo.an.base && src < P->memo.an.end) return DNS_S_AN; if (src >= P->memo.ns.base && src < P->memo.ns.end) return DNS_S_NS; if (src >= P->memo.ar.base && src < P->memo.ar.end) return DNS_S_AR; /* NOTE: Possibly bad memoization. Try it the hard-way. */ for (rp = 12, index = 0; rp < src && rp < P->end; index++) rp = dns_rr_skip(rp, P); section = DNS_S_QD; count = dns_p_count(P, section); while (index >= count && section <= DNS_S_AR) { section <<= 1; count += dns_p_count(P, section); } return DNS_S_ALL & section; } /* dns_rr_section() */ static enum dns_type dns_rr_type(unsigned short src, struct dns_packet *P) { struct dns_rr rr; int error; if ((error = dns_rr_parse(&rr, src, P))) return 0; return rr.type; } /* dns_rr_type() */ int dns_rr_cmp(struct dns_rr *r0, struct dns_packet *P0, struct dns_rr *r1, struct dns_packet *P1) { char host0[DNS_D_MAXNAME + 1], host1[DNS_D_MAXNAME + 1]; union dns_any any0, any1; int cmp, error; size_t len; if ((cmp = r0->type - r1->type)) return cmp; if ((cmp = r0->class - r1->class)) return cmp; /* * FIXME: Do label-by-label comparison to handle illegally long names? */ if (!(len = dns_d_expand(host0, sizeof host0, r0->dn.p, P0, &error)) || len >= sizeof host0) return -1; if (!(len = dns_d_expand(host1, sizeof host1, r1->dn.p, P1, &error)) || len >= sizeof host1) return 1; if ((cmp = strcasecmp(host0, host1))) return cmp; if (DNS_S_QD & (r0->section | r1->section)) { if (r0->section == r1->section) return 0; return (r0->section == DNS_S_QD)? -1 : 1; } if ((error = dns_any_parse(&any0, r0, P0))) return -1; if ((error = dns_any_parse(&any1, r1, P1))) return 1; return dns_any_cmp(&any0, r0->type, &any1, r1->type); } /* dns_rr_cmp() */ static _Bool dns_rr_exists(struct dns_rr *rr0, struct dns_packet *P0, struct dns_packet *P1) { struct dns_rr rr1; dns_rr_foreach(&rr1, P1, .section = rr0->section, .type = rr0->type) { if (0 == dns_rr_cmp(rr0, P0, &rr1, P1)) return 1; } return 0; } /* dns_rr_exists() */ static unsigned short dns_rr_offset(struct dns_rr *rr) { return rr->dn.p; } /* dns_rr_offset() */ static _Bool dns_rr_i_match(struct dns_rr *rr, struct dns_rr_i *i, struct dns_packet *P) { if (i->section && !(rr->section & i->section)) return 0; if (i->type && rr->type != i->type && i->type != DNS_T_ALL) return 0; if (i->class && rr->class != i->class && i->class != DNS_C_ANY) return 0; if (i->name) { char dn[DNS_D_MAXNAME + 1]; size_t len; int error; if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, P, &error)) || len >= sizeof dn) return 0; if (0 != strcasecmp(dn, i->name)) return 0; } if (i->data && i->type && rr->section > DNS_S_QD) { union dns_any rd; int error; if ((error = dns_any_parse(&rd, rr, P))) return 0; if (0 != dns_any_cmp(&rd, rr->type, i->data, i->type)) return 0; } return 1; } /* dns_rr_i_match() */ static unsigned short dns_rr_i_start(struct dns_rr_i *i, struct dns_packet *P) { unsigned short rp; struct dns_rr r0, rr; int error; if ((i->section & DNS_S_QD) && P->memo.qd.base) rp = P->memo.qd.base; else if ((i->section & DNS_S_AN) && P->memo.an.base) rp = P->memo.an.base; else if ((i->section & DNS_S_NS) && P->memo.ns.base) rp = P->memo.ns.base; else if ((i->section & DNS_S_AR) && P->memo.ar.base) rp = P->memo.ar.base; else rp = 12; for (; rp < P->end; rp = dns_rr_skip(rp, P)) { if ((error = dns_rr_parse(&rr, rp, P))) continue; rr.section = dns_rr_section(rp, P); if (!dns_rr_i_match(&rr, i, P)) continue; r0 = rr; goto lower; } return P->end; lower: if (i->sort == &dns_rr_i_packet) return dns_rr_offset(&r0); while ((rp = dns_rr_skip(rp, P)) < P->end) { if ((error = dns_rr_parse(&rr, rp, P))) continue; rr.section = dns_rr_section(rp, P); if (!dns_rr_i_match(&rr, i, P)) continue; if (i->sort(&rr, &r0, i, P) < 0) r0 = rr; } return dns_rr_offset(&r0); } /* dns_rr_i_start() */ static unsigned short dns_rr_i_skip(unsigned short rp, struct dns_rr_i *i, struct dns_packet *P) { struct dns_rr r0, r1, rr; int error; if ((error = dns_rr_parse(&r0, rp, P))) return P->end; r0.section = dns_rr_section(rp, P); rp = (i->sort == &dns_rr_i_packet)? dns_rr_skip(rp, P) : 12; for (; rp < P->end; rp = dns_rr_skip(rp, P)) { if ((error = dns_rr_parse(&rr, rp, P))) continue; rr.section = dns_rr_section(rp, P); if (!dns_rr_i_match(&rr, i, P)) continue; if (i->sort(&rr, &r0, i, P) <= 0) continue; r1 = rr; goto lower; } return P->end; lower: if (i->sort == &dns_rr_i_packet) return dns_rr_offset(&r1); while ((rp = dns_rr_skip(rp, P)) < P->end) { if ((error = dns_rr_parse(&rr, rp, P))) continue; rr.section = dns_rr_section(rp, P); if (!dns_rr_i_match(&rr, i, P)) continue; if (i->sort(&rr, &r0, i, P) <= 0) continue; if (i->sort(&rr, &r1, i, P) >= 0) continue; r1 = rr; } return dns_rr_offset(&r1); } /* dns_rr_i_skip() */ int dns_rr_i_packet(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { (void)i; (void)P; return (int)a->dn.p - (int)b->dn.p; } /* dns_rr_i_packet() */ int dns_rr_i_order(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { int cmp; (void)i; if ((cmp = a->section - b->section)) return cmp; if (a->type != b->type) return (int)a->dn.p - (int)b->dn.p; return dns_rr_cmp(a, P, b, P); } /* dns_rr_i_order() */ int dns_rr_i_shuffle(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { int cmp; (void)i; (void)P; while (!i->state.regs[0]) i->state.regs[0] = dns_random(); if ((cmp = a->section - b->section)) return cmp; return dns_k_shuffle16(a->dn.p, i->state.regs[0]) - dns_k_shuffle16(b->dn.p, i->state.regs[0]); } /* dns_rr_i_shuffle() */ void dns_rr_i_init(struct dns_rr_i *i) { static const struct dns_rr_i i_initializer; i->state = i_initializer.state; i->saved = i->state; } /* dns_rr_i_init() */ unsigned dns_rr_grep(struct dns_rr *rr, unsigned lim, struct dns_rr_i *i, struct dns_packet *P, int *error_) { unsigned count = 0; int error; switch (i->state.exec) { case 0: if (!i->sort) i->sort = &dns_rr_i_packet; i->state.next = dns_rr_i_start(i, P); i->state.exec++; /* FALL THROUGH */ case 1: while (count < lim && i->state.next < P->end) { if ((error = dns_rr_parse(rr, i->state.next, P))) goto error; rr->section = dns_rr_section(i->state.next, P); rr++; count++; i->state.count++; i->state.next = dns_rr_i_skip(i->state.next, i, P); } /* while() */ break; } /* switch() */ return count; error: if (error_) *error_ = error; return count; } /* dns_rr_grep() */ size_t dns_rr_print(void *_dst, size_t lim, struct dns_rr *rr, struct dns_packet *P, int *_error) { struct dns_buf dst = DNS_B_INTO(_dst, lim); union dns_any any; size_t n; int error; char __dst[DNS_STRMAXLEN + 1] = { 0 }; if (rr->section == DNS_S_QD) dns_b_putc(&dst, ';'); if (!(n = dns_d_expand(any.ns.host, sizeof any.ns.host, rr->dn.p, P, &error))) goto error; dns_b_put(&dst, any.ns.host, DNS_PP_MIN(n, sizeof any.ns.host - 1)); if (rr->section != DNS_S_QD) { dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, rr->ttl, 0); } dns_b_putc(&dst, ' '); dns_b_puts(&dst, dns_strclass(rr->class, __dst)); dns_b_putc(&dst, ' '); dns_b_puts(&dst, dns_strtype(rr->type, __dst)); if (rr->section == DNS_S_QD) goto epilog; dns_b_putc(&dst, ' '); if ((error = dns_any_parse(dns_any_init(&any, sizeof any), rr, P))) goto error; n = dns_any_print(dst.p, dst.pe - dst.p, &any, rr->type); dst.p += DNS_PP_MIN(n, (size_t)(dst.pe - dst.p)); epilog: return dns_b_strllen(&dst); error: *_error = error; return 0; } /* dns_rr_print() */ int dns_a_parse(struct dns_a *a, struct dns_rr *rr, struct dns_packet *P) { unsigned long addr; if (rr->rd.len != 4) return DNS_EILLEGAL; addr = ((0xffU & P->data[rr->rd.p + 0]) << 24) | ((0xffU & P->data[rr->rd.p + 1]) << 16) | ((0xffU & P->data[rr->rd.p + 2]) << 8) | ((0xffU & P->data[rr->rd.p + 3]) << 0); a->addr.s_addr = htonl(addr); return 0; } /* dns_a_parse() */ int dns_a_push(struct dns_packet *P, struct dns_a *a) { unsigned long addr; if (P->size - P->end < 6) return DNS_ENOBUFS; P->data[P->end++] = 0x00; P->data[P->end++] = 0x04; addr = ntohl(a->addr.s_addr); P->data[P->end++] = 0xffU & (addr >> 24); P->data[P->end++] = 0xffU & (addr >> 16); P->data[P->end++] = 0xffU & (addr >> 8); P->data[P->end++] = 0xffU & (addr >> 0); return 0; } /* dns_a_push() */ size_t dns_a_arpa(void *_dst, size_t lim, const struct dns_a *a) { struct dns_buf dst = DNS_B_INTO(_dst, lim); unsigned long octets = ntohl(a->addr.s_addr); unsigned i; for (i = 0; i < 4; i++) { dns_b_fmtju(&dst, 0xff & octets, 0); dns_b_putc(&dst, '.'); octets >>= 8; } dns_b_puts(&dst, "in-addr.arpa."); return dns_b_strllen(&dst); } /* dns_a_arpa() */ int dns_a_cmp(const struct dns_a *a, const struct dns_a *b) { if (ntohl(a->addr.s_addr) < ntohl(b->addr.s_addr)) return -1; if (ntohl(a->addr.s_addr) > ntohl(b->addr.s_addr)) return 1; return 0; } /* dns_a_cmp() */ size_t dns_a_print(void *dst, size_t lim, struct dns_a *a) { char addr[INET_ADDRSTRLEN + 1] = "0.0.0.0"; dns_inet_ntop(AF_INET, &a->addr, addr, sizeof addr); return dns_strlcpy(dst, addr, lim); } /* dns_a_print() */ int dns_aaaa_parse(struct dns_aaaa *aaaa, struct dns_rr *rr, struct dns_packet *P) { if (rr->rd.len != sizeof aaaa->addr.s6_addr) return DNS_EILLEGAL; memcpy(aaaa->addr.s6_addr, &P->data[rr->rd.p], sizeof aaaa->addr.s6_addr); return 0; } /* dns_aaaa_parse() */ int dns_aaaa_push(struct dns_packet *P, struct dns_aaaa *aaaa) { if (P->size - P->end < 2 + sizeof aaaa->addr.s6_addr) return DNS_ENOBUFS; P->data[P->end++] = 0x00; P->data[P->end++] = 0x10; memcpy(&P->data[P->end], aaaa->addr.s6_addr, sizeof aaaa->addr.s6_addr); P->end += sizeof aaaa->addr.s6_addr; return 0; } /* dns_aaaa_push() */ int dns_aaaa_cmp(const struct dns_aaaa *a, const struct dns_aaaa *b) { unsigned i; int cmp; for (i = 0; i < lengthof(a->addr.s6_addr); i++) { if ((cmp = (a->addr.s6_addr[i] - b->addr.s6_addr[i]))) return cmp; } return 0; } /* dns_aaaa_cmp() */ size_t dns_aaaa_arpa(void *_dst, size_t lim, const struct dns_aaaa *aaaa) { static const unsigned char hex[16] = "0123456789abcdef"; struct dns_buf dst = DNS_B_INTO(_dst, lim); unsigned nyble; int i, j; for (i = sizeof aaaa->addr.s6_addr - 1; i >= 0; i--) { nyble = aaaa->addr.s6_addr[i]; for (j = 0; j < 2; j++) { dns_b_putc(&dst, hex[0x0f & nyble]); dns_b_putc(&dst, '.'); nyble >>= 4; } } dns_b_puts(&dst, "ip6.arpa."); return dns_b_strllen(&dst); } /* dns_aaaa_arpa() */ size_t dns_aaaa_print(void *dst, size_t lim, struct dns_aaaa *aaaa) { char addr[INET6_ADDRSTRLEN + 1] = "::"; dns_inet_ntop(AF_INET6, &aaaa->addr, addr, sizeof addr); return dns_strlcpy(dst, addr, lim); } /* dns_aaaa_print() */ int dns_mx_parse(struct dns_mx *mx, struct dns_rr *rr, struct dns_packet *P) { size_t len; int error; if (rr->rd.len < 3) return DNS_EILLEGAL; mx->preference = (0xff00 & (P->data[rr->rd.p + 0] << 8)) | (0x00ff & (P->data[rr->rd.p + 1] << 0)); if (!(len = dns_d_expand(mx->host, sizeof mx->host, rr->rd.p + 2, P, &error))) return error; else if (len >= sizeof mx->host) return DNS_EILLEGAL; return 0; } /* dns_mx_parse() */ int dns_mx_push(struct dns_packet *P, struct dns_mx *mx) { size_t end, len; int error; if (P->size - P->end < 5) return DNS_ENOBUFS; end = P->end; P->end += 2; P->data[P->end++] = 0xff & (mx->preference >> 8); P->data[P->end++] = 0xff & (mx->preference >> 0); if ((error = dns_d_push(P, mx->host, strlen(mx->host)))) goto error; len = P->end - end - 2; P->data[end + 0] = 0xff & (len >> 8); P->data[end + 1] = 0xff & (len >> 0); return 0; error: P->end = end; return error; } /* dns_mx_push() */ int dns_mx_cmp(const struct dns_mx *a, const struct dns_mx *b) { int cmp; if ((cmp = a->preference - b->preference)) return cmp; return strcasecmp(a->host, b->host); } /* dns_mx_cmp() */ size_t dns_mx_print(void *_dst, size_t lim, struct dns_mx *mx) { struct dns_buf dst = DNS_B_INTO(_dst, lim); dns_b_fmtju(&dst, mx->preference, 0); dns_b_putc(&dst, ' '); dns_b_puts(&dst, mx->host); return dns_b_strllen(&dst); } /* dns_mx_print() */ size_t dns_mx_cname(void *dst, size_t lim, struct dns_mx *mx) { return dns_strlcpy(dst, mx->host, lim); } /* dns_mx_cname() */ int dns_ns_parse(struct dns_ns *ns, struct dns_rr *rr, struct dns_packet *P) { size_t len; int error; if (!(len = dns_d_expand(ns->host, sizeof ns->host, rr->rd.p, P, &error))) return error; else if (len >= sizeof ns->host) return DNS_EILLEGAL; return 0; } /* dns_ns_parse() */ int dns_ns_push(struct dns_packet *P, struct dns_ns *ns) { size_t end, len; int error; if (P->size - P->end < 3) return DNS_ENOBUFS; end = P->end; P->end += 2; if ((error = dns_d_push(P, ns->host, strlen(ns->host)))) goto error; len = P->end - end - 2; P->data[end + 0] = 0xff & (len >> 8); P->data[end + 1] = 0xff & (len >> 0); return 0; error: P->end = end; return error; } /* dns_ns_push() */ int dns_ns_cmp(const struct dns_ns *a, const struct dns_ns *b) { return strcasecmp(a->host, b->host); } /* dns_ns_cmp() */ size_t dns_ns_print(void *dst, size_t lim, struct dns_ns *ns) { return dns_strlcpy(dst, ns->host, lim); } /* dns_ns_print() */ size_t dns_ns_cname(void *dst, size_t lim, struct dns_ns *ns) { return dns_strlcpy(dst, ns->host, lim); } /* dns_ns_cname() */ int dns_cname_parse(struct dns_cname *cname, struct dns_rr *rr, struct dns_packet *P) { return dns_ns_parse((struct dns_ns *)cname, rr, P); } /* dns_cname_parse() */ int dns_cname_push(struct dns_packet *P, struct dns_cname *cname) { return dns_ns_push(P, (struct dns_ns *)cname); } /* dns_cname_push() */ int dns_cname_cmp(const struct dns_cname *a, const struct dns_cname *b) { return strcasecmp(a->host, b->host); } /* dns_cname_cmp() */ size_t dns_cname_print(void *dst, size_t lim, struct dns_cname *cname) { return dns_ns_print(dst, lim, (struct dns_ns *)cname); } /* dns_cname_print() */ size_t dns_cname_cname(void *dst, size_t lim, struct dns_cname *cname) { return dns_strlcpy(dst, cname->host, lim); } /* dns_cname_cname() */ int dns_soa_parse(struct dns_soa *soa, struct dns_rr *rr, struct dns_packet *P) { struct { void *dst; size_t lim; } dn[] = { { soa->mname, sizeof soa->mname }, { soa->rname, sizeof soa->rname } }; unsigned *ts[] = { &soa->serial, &soa->refresh, &soa->retry, &soa->expire, &soa->minimum }; unsigned short rp; unsigned i, j, n; int error; /* MNAME / RNAME */ if ((rp = rr->rd.p) >= P->end) return DNS_EILLEGAL; for (i = 0; i < lengthof(dn); i++) { if (!(n = dns_d_expand(dn[i].dst, dn[i].lim, rp, P, &error))) return error; else if (n >= dn[i].lim) return DNS_EILLEGAL; if ((rp = dns_d_skip(rp, P)) >= P->end) return DNS_EILLEGAL; } /* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */ for (i = 0; i < lengthof(ts); i++) { for (j = 0; j < 4; j++, rp++) { if (rp >= P->end) return DNS_EILLEGAL; *ts[i] <<= 8; *ts[i] |= (0xff & P->data[rp]); } } return 0; } /* dns_soa_parse() */ int dns_soa_push(struct dns_packet *P, struct dns_soa *soa) { void *dn[] = { soa->mname, soa->rname }; unsigned ts[] = { (0xffffffff & soa->serial), (0x7fffffff & soa->refresh), (0x7fffffff & soa->retry), (0x7fffffff & soa->expire), (0xffffffff & soa->minimum) }; unsigned i, j; size_t end, len; int error; end = P->end; if ((P->end += 2) >= P->size) goto toolong; /* MNAME / RNAME */ for (i = 0; i < lengthof(dn); i++) { if ((error = dns_d_push(P, dn[i], strlen(dn[i])))) goto error; } /* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */ for (i = 0; i < lengthof(ts); i++) { if ((P->end += 4) >= P->size) goto toolong; for (j = 1; j <= 4; j++) { P->data[P->end - j] = (0xff & ts[i]); ts[i] >>= 8; } } len = P->end - end - 2; P->data[end + 0] = (0xff & (len >> 8)); P->data[end + 1] = (0xff & (len >> 0)); return 0; toolong: error = DNS_ENOBUFS; /* FALL THROUGH */ error: P->end = end; return error; } /* dns_soa_push() */ int dns_soa_cmp(const struct dns_soa *a, const struct dns_soa *b) { int cmp; if ((cmp = strcasecmp(a->mname, b->mname))) return cmp; if ((cmp = strcasecmp(a->rname, b->rname))) return cmp; if (a->serial > b->serial) return -1; else if (a->serial < b->serial) return 1; if (a->refresh > b->refresh) return -1; else if (a->refresh < b->refresh) return 1; if (a->retry > b->retry) return -1; else if (a->retry < b->retry) return 1; if (a->expire > b->expire) return -1; else if (a->expire < b->expire) return 1; if (a->minimum > b->minimum) return -1; else if (a->minimum < b->minimum) return 1; return 0; } /* dns_soa_cmp() */ size_t dns_soa_print(void *_dst, size_t lim, struct dns_soa *soa) { struct dns_buf dst = DNS_B_INTO(_dst, lim); dns_b_puts(&dst, soa->mname); dns_b_putc(&dst, ' '); dns_b_puts(&dst, soa->rname); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, soa->serial, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, soa->refresh, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, soa->retry, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, soa->expire, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, soa->minimum, 0); return dns_b_strllen(&dst); } /* dns_soa_print() */ int dns_srv_parse(struct dns_srv *srv, struct dns_rr *rr, struct dns_packet *P) { unsigned short rp; unsigned i; size_t n; int error; memset(srv, '\0', sizeof *srv); rp = rr->rd.p; if (rr->rd.len < 7) return DNS_EILLEGAL; for (i = 0; i < 2; i++, rp++) { srv->priority <<= 8; srv->priority |= (0xff & P->data[rp]); } for (i = 0; i < 2; i++, rp++) { srv->weight <<= 8; srv->weight |= (0xff & P->data[rp]); } for (i = 0; i < 2; i++, rp++) { srv->port <<= 8; srv->port |= (0xff & P->data[rp]); } if (!(n = dns_d_expand(srv->target, sizeof srv->target, rp, P, &error))) return error; else if (n >= sizeof srv->target) return DNS_EILLEGAL; return 0; } /* dns_srv_parse() */ int dns_srv_push(struct dns_packet *P, struct dns_srv *srv) { size_t end, len; int error; end = P->end; if (P->size - P->end < 2) goto toolong; P->end += 2; if (P->size - P->end < 6) goto toolong; P->data[P->end++] = 0xff & (srv->priority >> 8); P->data[P->end++] = 0xff & (srv->priority >> 0); P->data[P->end++] = 0xff & (srv->weight >> 8); P->data[P->end++] = 0xff & (srv->weight >> 0); P->data[P->end++] = 0xff & (srv->port >> 8); P->data[P->end++] = 0xff & (srv->port >> 0); if (0 == (len = dns_d_comp(&P->data[P->end], P->size - P->end, srv->target, strlen(srv->target), P, &error))) goto error; else if (P->size - P->end < len) goto toolong; P->end += len; if (P->end > 65535) goto toolong; len = P->end - end - 2; P->data[end + 0] = 0xff & (len >> 8); P->data[end + 1] = 0xff & (len >> 0); return 0; toolong: error = DNS_ENOBUFS; /* FALL THROUGH */ error: P->end = end; return error; } /* dns_srv_push() */ int dns_srv_cmp(const struct dns_srv *a, const struct dns_srv *b) { int cmp; if ((cmp = a->priority - b->priority)) return cmp; /* * FIXME: We need some sort of random seed to implement the dynamic * weighting required by RFC 2782. */ if ((cmp = a->weight - b->weight)) return cmp; if ((cmp = a->port - b->port)) return cmp; return strcasecmp(a->target, b->target); } /* dns_srv_cmp() */ size_t dns_srv_print(void *_dst, size_t lim, struct dns_srv *srv) { struct dns_buf dst = DNS_B_INTO(_dst, lim); dns_b_fmtju(&dst, srv->priority, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, srv->weight, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, srv->port, 0); dns_b_putc(&dst, ' '); dns_b_puts(&dst, srv->target); return dns_b_strllen(&dst); } /* dns_srv_print() */ size_t dns_srv_cname(void *dst, size_t lim, struct dns_srv *srv) { return dns_strlcpy(dst, srv->target, lim); } /* dns_srv_cname() */ unsigned int dns_opt_ttl(const struct dns_opt *opt) { unsigned int ttl = 0; ttl |= (0xffU & opt->rcode) << 24; ttl |= (0xffU & opt->version) << 16; ttl |= (0xffffU & opt->flags) << 0; return ttl; } /* dns_opt_ttl() */ unsigned short dns_opt_class(const struct dns_opt *opt) { return opt->maxudp; } /* dns_opt_class() */ struct dns_opt *dns_opt_init(struct dns_opt *opt, size_t size) { assert(size >= offsetof(struct dns_opt, data)); opt->size = size - offsetof(struct dns_opt, data); opt->len = 0; opt->rcode = 0; opt->version = 0; opt->maxudp = 0; return opt; } /* dns_opt_init() */ static union dns_any *dns_opt_initany(union dns_any *any, size_t size) { return dns_opt_init(&any->opt, size), any; } /* dns_opt_initany() */ int dns_opt_parse(struct dns_opt *opt, struct dns_rr *rr, struct dns_packet *P) { const struct dns_buf src = DNS_B_FROM(&P->data[rr->rd.p], rr->rd.len); struct dns_buf dst = DNS_B_INTO(opt->data, opt->size); int error; opt->rcode = 0xfff & ((rr->ttl >> 20) | dns_header(P)->rcode); opt->version = 0xff & (rr->ttl >> 16); opt->flags = 0xffff & rr->ttl; opt->maxudp = 0xffff & rr->class; while (src.p < src.pe) { int code, len; if (-1 == (code = dns_b_get16(&src, -1))) return src.error; if (-1 == (len = dns_b_get16(&src, -1))) return src.error; switch (code) { default: dns_b_put16(&dst, code); dns_b_put16(&dst, len); if ((error = dns_b_move(&dst, &src, len))) return error; break; } } return 0; } /* dns_opt_parse() */ int dns_opt_push(struct dns_packet *P, struct dns_opt *opt) { const struct dns_buf src = DNS_B_FROM(opt->data, opt->len); struct dns_buf dst = DNS_B_INTO(&P->data[P->end], (P->size - P->end)); int error; /* rdata length (see below) */ if ((error = dns_b_put16(&dst, 0))) goto error; /* ... push known options here */ /* push opaque option data */ if ((error = dns_b_move(&dst, &src, (size_t)(src.pe - src.p)))) goto error; /* rdata length */ if ((error = dns_b_pput16(&dst, dns_b_tell(&dst) - 2, 0))) goto error; #if !DNS_DEBUG_OPT_FORMERR P->end += dns_b_tell(&dst); #endif return 0; error: return error; } /* dns_opt_push() */ int dns_opt_cmp(const struct dns_opt *a, const struct dns_opt *b) { (void)a; (void)b; return -1; } /* dns_opt_cmp() */ size_t dns_opt_print(void *_dst, size_t lim, struct dns_opt *opt) { struct dns_buf dst = DNS_B_INTO(_dst, lim); size_t p; dns_b_putc(&dst, '"'); for (p = 0; p < opt->len; p++) { dns_b_putc(&dst, '\\'); dns_b_fmtju(&dst, opt->data[p], 3); } dns_b_putc(&dst, '"'); return dns_b_strllen(&dst); } /* dns_opt_print() */ int dns_ptr_parse(struct dns_ptr *ptr, struct dns_rr *rr, struct dns_packet *P) { return dns_ns_parse((struct dns_ns *)ptr, rr, P); } /* dns_ptr_parse() */ int dns_ptr_push(struct dns_packet *P, struct dns_ptr *ptr) { return dns_ns_push(P, (struct dns_ns *)ptr); } /* dns_ptr_push() */ size_t dns_ptr_qname(void *dst, size_t lim, int af, void *addr) { switch (af) { case AF_INET6: return dns_aaaa_arpa(dst, lim, addr); case AF_INET: return dns_a_arpa(dst, lim, addr); default: { struct dns_a a; a.addr.s_addr = INADDR_NONE; return dns_a_arpa(dst, lim, &a); } } } /* dns_ptr_qname() */ int dns_ptr_cmp(const struct dns_ptr *a, const struct dns_ptr *b) { return strcasecmp(a->host, b->host); } /* dns_ptr_cmp() */ size_t dns_ptr_print(void *dst, size_t lim, struct dns_ptr *ptr) { return dns_ns_print(dst, lim, (struct dns_ns *)ptr); } /* dns_ptr_print() */ size_t dns_ptr_cname(void *dst, size_t lim, struct dns_ptr *ptr) { return dns_strlcpy(dst, ptr->host, lim); } /* dns_ptr_cname() */ int dns_sshfp_parse(struct dns_sshfp *fp, struct dns_rr *rr, struct dns_packet *P) { unsigned p = rr->rd.p, pe = rr->rd.p + rr->rd.len; if (pe - p < 2) return DNS_EILLEGAL; fp->algo = P->data[p++]; fp->type = P->data[p++]; switch (fp->type) { case DNS_SSHFP_SHA1: if (pe - p < sizeof fp->digest.sha1) return DNS_EILLEGAL; memcpy(fp->digest.sha1, &P->data[p], sizeof fp->digest.sha1); break; default: break; } /* switch() */ return 0; } /* dns_sshfp_parse() */ int dns_sshfp_push(struct dns_packet *P, struct dns_sshfp *fp) { unsigned p = P->end, pe = P->size, n; if (pe - p < 4) return DNS_ENOBUFS; p += 2; P->data[p++] = 0xff & fp->algo; P->data[p++] = 0xff & fp->type; switch (fp->type) { case DNS_SSHFP_SHA1: if (pe - p < sizeof fp->digest.sha1) return DNS_ENOBUFS; memcpy(&P->data[p], fp->digest.sha1, sizeof fp->digest.sha1); p += sizeof fp->digest.sha1; break; default: return DNS_EILLEGAL; } /* switch() */ n = p - P->end - 2; P->data[P->end++] = 0xff & (n >> 8); P->data[P->end++] = 0xff & (n >> 0); P->end = p; return 0; } /* dns_sshfp_push() */ int dns_sshfp_cmp(const struct dns_sshfp *a, const struct dns_sshfp *b) { int cmp; if ((cmp = a->algo - b->algo) || (cmp = a->type - b->type)) return cmp; switch (a->type) { case DNS_SSHFP_SHA1: return memcmp(a->digest.sha1, b->digest.sha1, sizeof a->digest.sha1); default: return 0; } /* switch() */ /* NOT REACHED */ } /* dns_sshfp_cmp() */ size_t dns_sshfp_print(void *_dst, size_t lim, struct dns_sshfp *fp) { static const unsigned char hex[16] = "0123456789abcdef"; struct dns_buf dst = DNS_B_INTO(_dst, lim); size_t i; dns_b_fmtju(&dst, fp->algo, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, fp->type, 0); dns_b_putc(&dst, ' '); switch (fp->type) { case DNS_SSHFP_SHA1: for (i = 0; i < sizeof fp->digest.sha1; i++) { dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 4)]); dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 0)]); } break; default: dns_b_putc(&dst, '0'); break; } /* switch() */ return dns_b_strllen(&dst); } /* dns_sshfp_print() */ struct dns_txt *dns_txt_init(struct dns_txt *txt, size_t size) { assert(size > offsetof(struct dns_txt, data)); txt->size = size - offsetof(struct dns_txt, data); txt->len = 0; return txt; } /* dns_txt_init() */ static union dns_any *dns_txt_initany(union dns_any *any, size_t size) { /* NB: union dns_any is already initialized as struct dns_txt */ (void)size; return any; } /* dns_txt_initany() */ int dns_txt_parse(struct dns_txt *txt, struct dns_rr *rr, struct dns_packet *P) { struct { unsigned char *b; size_t p, end; } dst, src; unsigned n; dst.b = txt->data; dst.p = 0; dst.end = txt->size; src.b = P->data; src.p = rr->rd.p; src.end = src.p + rr->rd.len; while (src.p < src.end) { n = 0xff & P->data[src.p++]; if (src.end - src.p < n || dst.end - dst.p < n) return DNS_EILLEGAL; memcpy(&dst.b[dst.p], &src.b[src.p], n); dst.p += n; src.p += n; } txt->len = dst.p; return 0; } /* dns_txt_parse() */ int dns_txt_push(struct dns_packet *P, struct dns_txt *txt) { struct { unsigned char *b; size_t p, end; } dst, src; unsigned n; dst.b = P->data; dst.p = P->end; dst.end = P->size; src.b = txt->data; src.p = 0; src.end = txt->len; if (dst.end - dst.p < 2) return DNS_ENOBUFS; n = txt->len + ((txt->len + 254) / 255); dst.b[dst.p++] = 0xff & (n >> 8); dst.b[dst.p++] = 0xff & (n >> 0); while (src.p < src.end) { n = DNS_PP_MIN(255, src.end - src.p); if (dst.p >= dst.end) return DNS_ENOBUFS; dst.b[dst.p++] = n; if (dst.end - dst.p < n) return DNS_ENOBUFS; memcpy(&dst.b[dst.p], &src.b[src.p], n); dst.p += n; src.p += n; } P->end = dst.p; return 0; } /* dns_txt_push() */ int dns_txt_cmp(const struct dns_txt *a, const struct dns_txt *b) { (void)a; (void)b; return -1; } /* dns_txt_cmp() */ size_t dns_txt_print(void *_dst, size_t lim, struct dns_txt *txt) { struct dns_buf src = DNS_B_FROM(txt->data, txt->len); struct dns_buf dst = DNS_B_INTO(_dst, lim); unsigned i; if (src.p < src.pe) { do { dns_b_putc(&dst, '"'); for (i = 0; i < 256 && src.p < src.pe; i++, src.p++) { if (*src.p < 32 || *src.p > 126 || *src.p == '"' || *src.p == '\\') { dns_b_putc(&dst, '\\'); dns_b_fmtju(&dst, *src.p, 3); } else { dns_b_putc(&dst, *src.p); } } dns_b_putc(&dst, '"'); dns_b_putc(&dst, ' '); } while (src.p < src.pe); dns_b_popc(&dst); } else { dns_b_putc(&dst, '"'); dns_b_putc(&dst, '"'); } return dns_b_strllen(&dst); } /* dns_txt_print() */ /* Some of the function pointers of DNS_RRTYPES are initialized with * slighlly different functions, thus we can't use prototypes. */ DNS_PRAGMA_PUSH #if __clang__ #pragma clang diagnostic ignored "-Wstrict-prototypes" #elif DNS_GNUC_PREREQ(4,6,0) #pragma GCC diagnostic ignored "-Wstrict-prototypes" #endif static const struct dns_rrtype { enum dns_type type; const char *name; union dns_any *(*init)(union dns_any *, size_t); int (*parse)(); int (*push)(); int (*cmp)(); size_t (*print)(); size_t (*cname)(); } dns_rrtypes[] = { { DNS_T_A, "A", 0, &dns_a_parse, &dns_a_push, &dns_a_cmp, &dns_a_print, 0, }, { DNS_T_AAAA, "AAAA", 0, &dns_aaaa_parse, &dns_aaaa_push, &dns_aaaa_cmp, &dns_aaaa_print, 0, }, { DNS_T_MX, "MX", 0, &dns_mx_parse, &dns_mx_push, &dns_mx_cmp, &dns_mx_print, &dns_mx_cname, }, { DNS_T_NS, "NS", 0, &dns_ns_parse, &dns_ns_push, &dns_ns_cmp, &dns_ns_print, &dns_ns_cname, }, { DNS_T_CNAME, "CNAME", 0, &dns_cname_parse, &dns_cname_push, &dns_cname_cmp, &dns_cname_print, &dns_cname_cname, }, { DNS_T_SOA, "SOA", 0, &dns_soa_parse, &dns_soa_push, &dns_soa_cmp, &dns_soa_print, 0, }, { DNS_T_SRV, "SRV", 0, &dns_srv_parse, &dns_srv_push, &dns_srv_cmp, &dns_srv_print, &dns_srv_cname, }, { DNS_T_OPT, "OPT", &dns_opt_initany, &dns_opt_parse, &dns_opt_push, &dns_opt_cmp, &dns_opt_print, 0, }, { DNS_T_PTR, "PTR", 0, &dns_ptr_parse, &dns_ptr_push, &dns_ptr_cmp, &dns_ptr_print, &dns_ptr_cname, }, { DNS_T_TXT, "TXT", &dns_txt_initany, &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0, }, { DNS_T_SPF, "SPF", &dns_txt_initany, &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0, }, { DNS_T_SSHFP, "SSHFP", 0, &dns_sshfp_parse, &dns_sshfp_push, &dns_sshfp_cmp, &dns_sshfp_print, 0, }, { DNS_T_AXFR, "AXFR", 0, 0, 0, 0, 0, 0, }, }; /* dns_rrtypes[] */ DNS_PRAGMA_POP /*(-Wstrict-prototypes)*/ static const struct dns_rrtype *dns_rrtype(enum dns_type type) { const struct dns_rrtype *t; for (t = dns_rrtypes; t < endof(dns_rrtypes); t++) { if (t->type == type && t->parse) { return t; } } return NULL; } /* dns_rrtype() */ union dns_any *dns_any_init(union dns_any *any, size_t size) { dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type"); return (union dns_any *)dns_txt_init(&any->rdata, size); } /* dns_any_init() */ static size_t dns_any_sizeof(union dns_any *any) { dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type"); return offsetof(struct dns_txt, data) + any->rdata.size; } /* dns_any_sizeof() */ static union dns_any *dns_any_reinit(union dns_any *any, const struct dns_rrtype *t) { return (t->init)? t->init(any, dns_any_sizeof(any)) : any; } /* dns_any_reinit() */ int dns_any_parse(union dns_any *any, struct dns_rr *rr, struct dns_packet *P) { const struct dns_rrtype *t; if ((t = dns_rrtype(rr->type))) return t->parse(dns_any_reinit(any, t), rr, P); if (rr->rd.len > any->rdata.size) return DNS_EILLEGAL; memcpy(any->rdata.data, &P->data[rr->rd.p], rr->rd.len); any->rdata.len = rr->rd.len; return 0; } /* dns_any_parse() */ int dns_any_push(struct dns_packet *P, union dns_any *any, enum dns_type type) { const struct dns_rrtype *t; if ((t = dns_rrtype(type))) return t->push(P, any); if (P->size - P->end < any->rdata.len + 2) return DNS_ENOBUFS; P->data[P->end++] = 0xff & (any->rdata.len >> 8); P->data[P->end++] = 0xff & (any->rdata.len >> 0); memcpy(&P->data[P->end], any->rdata.data, any->rdata.len); P->end += any->rdata.len; return 0; } /* dns_any_push() */ int dns_any_cmp(const union dns_any *a, enum dns_type x, const union dns_any *b, enum dns_type y) { const struct dns_rrtype *t; int cmp; if ((cmp = x - y)) return cmp; if ((t = dns_rrtype(x))) return t->cmp(a, b); return -1; } /* dns_any_cmp() */ size_t dns_any_print(void *_dst, size_t lim, union dns_any *any, enum dns_type type) { const struct dns_rrtype *t; struct dns_buf src, dst; if ((t = dns_rrtype(type))) return t->print(_dst, lim, any); dns_b_from(&src, any->rdata.data, any->rdata.len); dns_b_into(&dst, _dst, lim); dns_b_putc(&dst, '"'); while (src.p < src.pe) { dns_b_putc(&dst, '\\'); dns_b_fmtju(&dst, *src.p++, 3); } dns_b_putc(&dst, '"'); return dns_b_strllen(&dst); } /* dns_any_print() */ size_t dns_any_cname(void *dst, size_t lim, union dns_any *any, enum dns_type type) { const struct dns_rrtype *t; if ((t = dns_rrtype(type)) && t->cname) return t->cname(dst, lim, any); return 0; } /* dns_any_cname() */ /* * E V E N T T R A C I N G R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include /* DBL_MANT_DIG */ #include /* PRIu64 */ /* for default trace ID generation try to fit in lua_Number, usually double */ #define DNS_TRACE_ID_BITS DNS_PP_MIN(DBL_MANT_DIG, (sizeof (dns_trace_id_t) * CHAR_BIT)) /* assuming FLT_RADIX == 2 */ #define DNS_TRACE_ID_MASK (((DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1)) - 1) | (DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1))) #define DNS_TRACE_ID_PRI PRIu64 static inline dns_trace_id_t dns_trace_mkid(void) { dns_trace_id_t id = 0; unsigned r; /* return type of dns_random() */ const size_t id_bit = sizeof id * CHAR_BIT; const size_t r_bit = sizeof r * CHAR_BIT; for (size_t n = 0; n < id_bit; n += r_bit) { r = dns_random(); id <<= r_bit; id |= r; } return DNS_TRACE_ID_MASK & id; } struct dns_trace { dns_atomic_t refcount; FILE *fp; dns_trace_id_t id; struct { struct dns_trace_cname { char host[DNS_D_MAXNAME + 1]; struct sockaddr_storage addr; } base[4]; size_t p; } cnames; }; static void dns_te_initname(struct sockaddr_storage *ss, int fd, int (* STDCALL f)(socket_fd_t, struct sockaddr *, socklen_t *)) { socklen_t n = sizeof *ss; if (0 != f(fd, (struct sockaddr *)ss, &n)) goto unspec; if (n > sizeof *ss) goto unspec; return; unspec: memset(ss, '\0', sizeof *ss); ss->ss_family = AF_UNSPEC; } static void dns_te_initnames(struct sockaddr_storage *local, struct sockaddr_storage *remote, int fd) { dns_te_initname(local, fd, &getsockname); dns_te_initname(remote, fd, &getpeername); } static struct dns_trace_event *dns_te_init(struct dns_trace_event *te, int type) { /* NB: silence valgrind */ memset(te, '\0', offsetof(struct dns_trace_event, data)); te->type = type; return te; } int dns_trace_abi(void) { return DNS_TRACE_ABI; } struct dns_trace *dns_trace_open(FILE *fp, dns_error_t *error) { static const struct dns_trace trace_initializer = { .refcount = 1 }; struct dns_trace *trace; if (!(trace = malloc(sizeof *trace))) goto syerr; *trace = trace_initializer; if (fp) { trace->fp = fp; } trace->id = dns_trace_mkid(); return trace; syerr: *error = dns_syerr(); dns_trace_close(trace); return NULL; } /* dns_trace_open() */ void dns_trace_close(struct dns_trace *trace) { if (!trace || 1 != dns_trace_release(trace)) return; if (trace->fp) fclose(trace->fp); free(trace); } /* dns_trace_close() */ dns_refcount_t dns_trace_acquire(struct dns_trace *trace) { return dns_atomic_fetch_add(&trace->refcount); } /* dns_trace_acquire() */ static struct dns_trace *dns_trace_acquire_p(struct dns_trace *trace) { return (trace)? dns_trace_acquire(trace), trace : NULL; } /* dns_trace_acquire_p() */ dns_refcount_t dns_trace_release(struct dns_trace *trace) { return dns_atomic_fetch_sub(&trace->refcount); } /* dns_trace_release() */ dns_trace_id_t dns_trace_id(struct dns_trace *trace) { return trace->id; } /* dns_trace_id() */ dns_trace_id_t dns_trace_setid(struct dns_trace *trace, dns_trace_id_t id) { trace->id = (id)? id : dns_trace_mkid(); return trace->id; } /* dns_trace_setid() */ struct dns_trace_event *dns_trace_get(struct dns_trace *trace, struct dns_trace_event **tp, dns_error_t *error) { return dns_trace_fget(tp, trace->fp, error); } /* dns_trace_get() */ dns_error_t dns_trace_put(struct dns_trace *trace, const struct dns_trace_event *te, const void *data, size_t datasize) { return dns_trace_fput(te, data, datasize, trace->fp); } /* dns_trace_put() */ struct dns_trace_event *dns_trace_tag(struct dns_trace *trace, struct dns_trace_event *te) { struct timeval tv; te->id = trace->id; gettimeofday(&tv, NULL); dns_tv2ts(&te->ts, &tv); te->abi = DNS_TRACE_ABI; return te; } /* dns_trace_tag() */ static dns_error_t dns_trace_tag_and_put(struct dns_trace *trace, struct dns_trace_event *te, const void *data, size_t datasize) { return dns_trace_put(trace, dns_trace_tag(trace, te), data, datasize); } /* dns_trace_tag_and_put() */ struct dns_trace_event *dns_trace_fget(struct dns_trace_event **tp, FILE *fp, dns_error_t *error) { const size_t headsize = offsetof(struct dns_trace_event, data); struct dns_trace_event tmp, *te; size_t n; errno = 0; if (!(n = fread(&tmp, 1, headsize, fp))) goto none; if (n < offsetof(struct dns_trace_event, data)) goto some; if (!(te = realloc(*tp, DNS_PP_MAX(headsize, tmp.size)))) { *error = errno; return NULL; } *tp = te; memcpy(te, &tmp, offsetof(struct dns_trace_event, data)); if (dns_te_datasize(te)) { errno = 0; if (!(n = fread(te->data, 1, dns_te_datasize(te), fp))) goto none; if (n < dns_te_datasize(te)) goto some; } return te; none: *error = (ferror(fp))? errno : 0; return NULL; some: *error = 0; return NULL; } dns_error_t dns_trace_fput(const struct dns_trace_event *te, const void *data, size_t datasize, FILE *fp) { size_t headsize = offsetof(struct dns_trace_event, data); struct dns_trace_event tmp; memcpy(&tmp, te, headsize); tmp.size = headsize + datasize; /* NB: ignore seek error as fp might not point to a regular file */ (void)fseek(fp, 0, SEEK_END); if (fwrite(&tmp, 1, headsize, fp) < headsize) return errno; if (data) if (fwrite(data, 1, datasize, fp) < datasize) return errno; if (fflush(fp)) return errno; return 0; } static void dns_trace_setcname(struct dns_trace *trace, const char *host, const struct sockaddr *addr) { struct dns_trace_cname *cname; if (!trace || !trace->fp) return; cname = &trace->cnames.base[trace->cnames.p]; dns_strlcpy(cname->host, host, sizeof cname->host); memcpy(&cname->addr, addr, DNS_PP_MIN(dns_sa_len(addr), sizeof cname->addr)); trace->cnames.p = (trace->cnames.p + 1) % lengthof(trace->cnames.base); } static const char *dns_trace_cname(struct dns_trace *trace, const struct sockaddr *addr) { if (!trace || !trace->fp) return NULL; /* NB: start search from the write cursor to */ for (const struct dns_trace_cname *cname = trace->cnames.base; cname < endof(trace->cnames.base); cname++) { if (0 == dns_sa_cmp((struct sockaddr *)addr, (struct sockaddr *)&cname->addr)) return cname->host; } return NULL; } static void dns_trace_res_submit(struct dns_trace *trace, const char *qname, enum dns_type qtype, enum dns_class qclass, int error) { struct dns_trace_event te; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_RES_SUBMIT); dns_strlcpy(te.res_submit.qname, qname, sizeof te.res_submit.qname); te.res_submit.qtype = qtype; te.res_submit.qclass = qclass; te.res_submit.error = error; dns_trace_tag_and_put(trace, &te, NULL, 0); } static void dns_trace_res_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) { struct dns_trace_event te; const void *data; size_t datasize; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_RES_FETCH); data = (packet)? packet->data : NULL; datasize = (packet)? packet->end : 0; te.res_fetch.error = error; dns_trace_tag_and_put(trace, &te, data, datasize); } static void dns_trace_so_submit(struct dns_trace *trace, const struct dns_packet *packet, const struct sockaddr *haddr, int error) { struct dns_trace_event te; const char *cname; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SO_SUBMIT); memcpy(&te.so_submit.haddr, haddr, DNS_PP_MIN(dns_sa_len(haddr), sizeof te.so_submit.haddr)); if ((cname = dns_trace_cname(trace, haddr))) dns_strlcpy(te.so_submit.hname, cname, sizeof te.so_submit.hname); te.so_submit.error = error; dns_trace_tag_and_put(trace, &te, packet->data, packet->end); } static void dns_trace_so_verify(struct dns_trace *trace, const struct dns_packet *packet, int error) { struct dns_trace_event te; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SO_VERIFY); te.so_verify.error = error; dns_trace_tag_and_put(trace, &te, packet->data, packet->end); } static void dns_trace_so_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) { struct dns_trace_event te; const void *data; size_t datasize; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SO_FETCH); data = (packet)? packet->data : NULL; datasize = (packet)? packet->end : 0; te.so_fetch.error = error; dns_trace_tag_and_put(trace, &te, data, datasize); } static void dns_trace_sys_connect(struct dns_trace *trace, int fd, int socktype, const struct sockaddr *dst, int error) { struct dns_trace_event te; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SYS_CONNECT); dns_te_initname(&te.sys_connect.src, fd, &getsockname); memcpy(&te.sys_connect.dst, dst, DNS_PP_MIN(dns_sa_len(dst), sizeof te.sys_connect.dst)); te.sys_connect.socktype = socktype; te.sys_connect.error = error; dns_trace_tag_and_put(trace, &te, NULL, 0); } static void dns_trace_sys_send(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) { struct dns_trace_event te; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SYS_SEND); dns_te_initnames(&te.sys_send.src, &te.sys_send.dst, fd); te.sys_send.socktype = socktype; te.sys_send.error = error; dns_trace_tag_and_put(trace, &te, data, datasize); } static void dns_trace_sys_recv(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) { struct dns_trace_event te; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SYS_RECV); dns_te_initnames(&te.sys_recv.dst, &te.sys_recv.src, fd); te.sys_recv.socktype = socktype; te.sys_recv.error = error; dns_trace_tag_and_put(trace, &te, data, datasize); } static dns_error_t dns_trace_dump_packet(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) { struct dns_packet *packet = NULL; char *line = NULL, *p; size_t size = 1, skip = 0; struct dns_rr_i records; struct dns_p_lines_i lines; size_t len, count; int error; if (!(packet = dns_p_make(datasize, &error))) goto error; memcpy(packet->data, data, datasize); packet->end = datasize; (void)dns_p_study(packet); resize: if (!(p = dns_reallocarray(line, size, 2, &error))) goto error; line = p; size *= 2; memset(&records, 0, sizeof records); memset(&lines, 0, sizeof lines); count = 0; while ((len = dns_p_lines(line, size, &error, packet, &records, &lines))) { if (!(len < size)) { skip = count; goto resize; } else if (skip <= count) { fputs(prefix, fp); fwrite(line, 1, len, fp); } count++; } if (error) goto error; error = 0; error: free(line); dns_p_free(packet); return error; } static dns_error_t dns_trace_dump_data(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) { struct dns_hxd_lines_i lines = { 0 }; char line[128]; size_t len; while ((len = dns_hxd_lines(line, sizeof line, data, datasize, &lines))) { if (len >= sizeof line) return EOVERFLOW; /* shouldn't be possible */ fputs(prefix, fp); fwrite(line, 1, len, fp); } return 0; } static dns_error_t dns_trace_dump_addr(struct dns_trace *trace, const char *prefix, const struct sockaddr_storage *ss, FILE *fp) { const void *addr; const char *path; socklen_t len; int error; if ((addr = dns_sa_addr(ss->ss_family, (struct sockaddr *)ss, NULL))) { char ip[INET6_ADDRSTRLEN + 1]; if ((error = dns_ntop(ss->ss_family, addr, ip, sizeof ip))) return error; fprintf(fp, "%s%s\n", prefix, ip); } else if ((path = dns_sa_path((struct sockaddr *)ss, &len))) { fprintf(fp, "%sunix:%.*s", prefix, (int)len, path); } else { return EINVAL; } return 0; } static dns_error_t dns_trace_dump_meta(struct dns_trace *trace, const char *prefix, const struct dns_trace_event *te, dns_microseconds_t elapsed, FILE *fp) { char time_s[48], elapsed_s[48]; dns_utime_print(time_s, sizeof time_s, dns_ts2us(&te->ts, 0)); dns_utime_print(elapsed_s, sizeof elapsed_s, elapsed); fprintf(fp, "%sid: %"DNS_TRACE_ID_PRI"\n", prefix, te->id); fprintf(fp, "%sts: %s (%s)\n", prefix, time_s, elapsed_s); fprintf(fp, "%sabi: 0x%x (0x%x)\n", prefix, te->abi, DNS_TRACE_ABI); return 0; } static dns_error_t dns_trace_dump_error(struct dns_trace *trace, const char *prefix, int error, FILE *fp) { fprintf(fp, "%s%d (%s)\n", prefix, error, (error)? dns_strerror(error) : "none"); return 0; } dns_error_t dns_trace_dump(struct dns_trace *trace, FILE *fp) { struct dns_trace_event *te = NULL; struct { dns_trace_id_t id; dns_microseconds_t begin, elapsed; } state = { 0 }; int error; char __dst[DNS_STRMAXLEN + 1] = { 0 }; if (!trace || !trace->fp) return EINVAL; if (0 != fseek(trace->fp, 0, SEEK_SET)) goto syerr; while (dns_trace_fget(&te, trace->fp, &error)) { size_t datasize = dns_te_datasize(te); const unsigned char *data = (datasize)? te->data : NULL; if (state.id != te->id) { state.id = te->id; state.begin = dns_ts2us(&te->ts, 0); } dns_time_diff(&state.elapsed, dns_ts2us(&te->ts, 0), state.begin); switch(te->type) { case DNS_TE_RES_SUBMIT: fprintf(fp, "dns_res_submit:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); fprintf(fp, " qname: %s\n", te->res_submit.qname); fprintf(fp, " qtype: %s\n", dns_strtype(te->res_submit.qtype, __dst)); fprintf(fp, " qclass: %s\n", dns_strclass(te->res_submit.qclass, __dst)); dns_trace_dump_error(trace, " error: ", te->res_submit.error, fp); break; case DNS_TE_RES_FETCH: fprintf(fp, "dns_res_fetch:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_error(trace, " error: ", te->res_fetch.error, fp); if (data) { fprintf(fp, " packet: |\n"); if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) goto error; fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; case DNS_TE_SO_SUBMIT: fprintf(fp, "dns_so_submit:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); fprintf(fp, " hname: %s\n", te->so_submit.hname); dns_trace_dump_addr(trace, " haddr: ", &te->so_submit.haddr, fp); dns_trace_dump_error(trace, " error: ", te->so_submit.error, fp); if (data) { fprintf(fp, " packet: |\n"); if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) goto error; fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; case DNS_TE_SO_VERIFY: fprintf(fp, "dns_so_verify:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_error(trace, " error: ", te->so_verify.error, fp); if (data) { fprintf(fp, " packet: |\n"); if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) goto error; fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; case DNS_TE_SO_FETCH: fprintf(fp, "dns_so_fetch:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_error(trace, " error: ", te->so_fetch.error, fp); if (data) { fprintf(fp, " packet: |\n"); if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) goto error; fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; case DNS_TE_SYS_CONNECT: { int socktype = te->sys_connect.socktype; fprintf(fp, "dns_sys_connect:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_addr(trace, " src: ", &te->sys_connect.src, fp); dns_trace_dump_addr(trace, " dst: ", &te->sys_connect.dst, fp); fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?")); dns_trace_dump_error(trace, " error: ", te->sys_connect.error, fp); break; } case DNS_TE_SYS_SEND: { int socktype = te->sys_send.socktype; fprintf(fp, "dns_sys_send:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_addr(trace, " src: ", &te->sys_send.src, fp); dns_trace_dump_addr(trace, " dst: ", &te->sys_send.dst, fp); fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?")); dns_trace_dump_error(trace, " error: ", te->sys_send.error, fp); if (data) { fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; } case DNS_TE_SYS_RECV: { int socktype = te->sys_recv.socktype; fprintf(fp, "dns_sys_recv:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_addr(trace, " src: ", &te->sys_recv.src, fp); dns_trace_dump_addr(trace, " dst: ", &te->sys_recv.dst, fp); fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?")); dns_trace_dump_error(trace, " error: ", te->sys_recv.error, fp); if (data) { fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; } default: fprintf(fp, "unknown(0x%.2x):\n", te->type); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); if (data) { fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; } } goto epilog; syerr: error = errno; error: (void)0; epilog: free(te); return error; } /* * H O S T S R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ struct dns_hosts { struct dns_hosts_entry { char host[DNS_D_MAXNAME + 1]; char arpa[73 + 1]; int af; union { struct in_addr a4; struct in6_addr a6; } addr; _Bool alias; struct dns_hosts_entry *next; } *head, **tail; dns_atomic_t refcount; }; /* struct dns_hosts */ struct dns_hosts *dns_hosts_open(int *error) { static const struct dns_hosts hosts_initializer = { .refcount = 1 }; struct dns_hosts *hosts; if (!(hosts = malloc(sizeof *hosts))) goto syerr; *hosts = hosts_initializer; hosts->tail = &hosts->head; return hosts; syerr: *error = dns_syerr(); free(hosts); return 0; } /* dns_hosts_open() */ void dns_hosts_close(struct dns_hosts *hosts) { struct dns_hosts_entry *ent, *xnt; if (!hosts || 1 != dns_hosts_release(hosts)) return; for (ent = hosts->head; ent; ent = xnt) { xnt = ent->next; free(ent); } free(hosts); return; } /* dns_hosts_close() */ dns_refcount_t dns_hosts_acquire(struct dns_hosts *hosts) { return dns_atomic_fetch_add(&hosts->refcount); } /* dns_hosts_acquire() */ dns_refcount_t dns_hosts_release(struct dns_hosts *hosts) { return dns_atomic_fetch_sub(&hosts->refcount); } /* dns_hosts_release() */ struct dns_hosts *dns_hosts_mortal(struct dns_hosts *hosts) { if (hosts) dns_hosts_release(hosts); return hosts; } /* dns_hosts_mortal() */ struct dns_hosts *dns_hosts_local(int *error_) { struct dns_hosts *hosts; int error; if (!(hosts = dns_hosts_open(&error))) goto error; if ((error = dns_hosts_loadpath(hosts, "/etc/hosts"))) goto error; return hosts; error: *error_ = error; dns_hosts_close(hosts); return 0; } /* dns_hosts_local() */ #define dns_hosts_issep(ch) (dns_isspace(ch)) #define dns_hosts_iscom(ch) ((ch) == '#' || (ch) == ';') int dns_hosts_loadfile(struct dns_hosts *hosts, FILE *fp) { struct dns_hosts_entry ent; char word[DNS_PP_MAX(INET6_ADDRSTRLEN, DNS_D_MAXNAME) + 1]; unsigned wp, wc, skip; int ch, error; rewind(fp); do { memset(&ent, '\0', sizeof ent); wc = 0; skip = 0; do { memset(word, '\0', sizeof word); wp = 0; while (EOF != (ch = fgetc(fp)) && ch != '\n') { skip |= !!dns_hosts_iscom(ch); if (skip) continue; if (dns_hosts_issep(ch)) break; if (wp < sizeof word - 1) word[wp] = ch; wp++; } if (!wp) continue; wc++; switch (wc) { case 0: break; case 1: ent.af = (strchr(word, ':'))? AF_INET6 : AF_INET; skip = (1 != dns_inet_pton(ent.af, word, &ent.addr)); break; default: if (!wp) break; dns_d_anchor(ent.host, sizeof ent.host, word, wp); if ((error = dns_hosts_insert(hosts, ent.af, &ent.addr, ent.host, (wc > 2)))) return error; break; } /* switch() */ } while (ch != EOF && ch != '\n'); } while (ch != EOF); return 0; } /* dns_hosts_loadfile() */ int dns_hosts_loadpath(struct dns_hosts *hosts, const char *path) { FILE *fp; int error; if (!(fp = dns_fopen(path, "rt", &error))) return error; error = dns_hosts_loadfile(hosts, fp); fclose(fp); return error; } /* dns_hosts_loadpath() */ int dns_hosts_dump(struct dns_hosts *hosts, FILE *fp) { struct dns_hosts_entry *ent, *xnt; char addr[INET6_ADDRSTRLEN + 1]; unsigned i; for (ent = hosts->head; ent; ent = xnt) { xnt = ent->next; dns_inet_ntop(ent->af, &ent->addr, addr, sizeof addr); fputs(addr, fp); for (i = strlen(addr); i < INET_ADDRSTRLEN; i++) fputc(' ', fp); fputc(' ', fp); fputs(ent->host, fp); fputc('\n', fp); } return 0; } /* dns_hosts_dump() */ int dns_hosts_insert(struct dns_hosts *hosts, int af, const void *addr, const void *host, _Bool alias) { struct dns_hosts_entry *ent; int error; if (!(ent = malloc(sizeof *ent))) goto syerr; dns_d_anchor(ent->host, sizeof ent->host, host, strlen(host)); switch ((ent->af = af)) { case AF_INET6: memcpy(&ent->addr.a6, addr, sizeof ent->addr.a6); dns_aaaa_arpa(ent->arpa, sizeof ent->arpa, addr); break; case AF_INET: memcpy(&ent->addr.a4, addr, sizeof ent->addr.a4); dns_a_arpa(ent->arpa, sizeof ent->arpa, addr); break; default: error = EINVAL; goto error; } /* switch() */ ent->alias = alias; ent->next = 0; *hosts->tail = ent; hosts->tail = &ent->next; return 0; syerr: error = dns_syerr(); error: free(ent); return error; } /* dns_hosts_insert() */ struct dns_packet *dns_hosts_query(struct dns_hosts *hosts, struct dns_packet *Q, int *error_) { union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 }; struct dns_packet *P = dns_p_init(&P_instance.p, 512); struct dns_packet *A = 0; struct dns_rr rr; struct dns_hosts_entry *ent; int error, af; char qname[DNS_D_MAXNAME + 1]; size_t qlen; if ((error = dns_rr_parse(&rr, 12, Q))) goto error; if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, Q, &error))) goto error; else if (qlen >= sizeof qname) goto toolong; if ((error = dns_p_push(P, DNS_S_QD, qname, qlen, rr.type, rr.class, 0, 0))) goto error; switch (rr.type) { case DNS_T_PTR: for (ent = hosts->head; ent; ent = ent->next) { if (ent->alias || 0 != strcasecmp(qname, ent->arpa)) continue; if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, ent->host))) goto error; } break; case DNS_T_AAAA: af = AF_INET6; goto loop; case DNS_T_A: af = AF_INET; loop: for (ent = hosts->head; ent; ent = ent->next) { if (ent->af != af || 0 != strcasecmp(qname, ent->host)) continue; if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, &ent->addr))) goto error; } break; default: break; } /* switch() */ if (!(A = dns_p_copy(dns_p_make(P->end, &error), P))) goto error; return A; toolong: error = DNS_EILLEGAL; error: *error_ = error; dns_p_free(A); return 0; } /* dns_hosts_query() */ /* * R E S O L V . C O N F R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ struct dns_resolv_conf *dns_resconf_open(int *error) { static const struct dns_resolv_conf resconf_initializer = { .lookup = "bf", .family = { AF_INET, AF_INET6 }, .options = { .ndots = 1, .timeout = 5, .attempts = 2, .tcp = DNS_RESCONF_TCP_ENABLE, }, .iface = { .ss_family = AF_INET }, }; struct dns_resolv_conf *resconf; struct sockaddr_in *sin; if (!(resconf = malloc(sizeof *resconf))) goto syerr; *resconf = resconf_initializer; sin = (struct sockaddr_in *)&resconf->nameserver[0]; sin->sin_family = AF_INET; sin->sin_addr.s_addr = INADDR_ANY; sin->sin_port = htons(53); #if defined(SA_LEN) sin->sin_len = sizeof *sin; #endif if (0 != gethostname(resconf->search[0], sizeof resconf->search[0])) goto syerr; /* * If gethostname() returned a string without any label * separator, then search[0][0] should be NUL. */ if (strchr (resconf->search[0], '.')) { dns_d_anchor(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0])); dns_d_cleave(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0])); } else { memset (resconf->search[0], 0, sizeof resconf->search[0]); } dns_resconf_acquire(resconf); return resconf; syerr: *error = dns_syerr(); free(resconf); return 0; } /* dns_resconf_open() */ void dns_resconf_close(struct dns_resolv_conf *resconf) { if (!resconf || 1 != dns_resconf_release(resconf)) return /* void */; free(resconf); } /* dns_resconf_close() */ dns_refcount_t dns_resconf_acquire(struct dns_resolv_conf *resconf) { return dns_atomic_fetch_add(&resconf->_.refcount); } /* dns_resconf_acquire() */ dns_refcount_t dns_resconf_release(struct dns_resolv_conf *resconf) { return dns_atomic_fetch_sub(&resconf->_.refcount); } /* dns_resconf_release() */ struct dns_resolv_conf *dns_resconf_mortal(struct dns_resolv_conf *resconf) { if (resconf) dns_resconf_release(resconf); return resconf; } /* dns_resconf_mortal() */ struct dns_resolv_conf *dns_resconf_local(int *error_) { struct dns_resolv_conf *resconf; int error; if (!(resconf = dns_resconf_open(&error))) goto error; if ((error = dns_resconf_loadpath(resconf, "/etc/resolv.conf"))) { /* * NOTE: Both the glibc and BIND9 resolvers ignore a missing * /etc/resolv.conf, defaulting to a nameserver of * 127.0.0.1. See also dns_hints_insert_resconf, and the * default initialization of nameserver[0] in * dns_resconf_open. */ if (error != ENOENT) goto error; } if ((error = dns_nssconf_loadpath(resconf, "/etc/nsswitch.conf"))) { if (error != ENOENT) goto error; } return resconf; error: *error_ = error; dns_resconf_close(resconf); return 0; } /* dns_resconf_local() */ struct dns_resolv_conf *dns_resconf_root(int *error) { struct dns_resolv_conf *resconf; if ((resconf = dns_resconf_local(error))) resconf->options.recurse = 1; return resconf; } /* dns_resconf_root() */ static time_t dns_resconf_timeout(const struct dns_resolv_conf *resconf) { return (time_t)DNS_PP_MIN(INT_MAX, resconf->options.timeout); } /* dns_resconf_timeout() */ enum dns_resconf_keyword { DNS_RESCONF_NAMESERVER, DNS_RESCONF_DOMAIN, DNS_RESCONF_SEARCH, DNS_RESCONF_LOOKUP, DNS_RESCONF_FILE, DNS_RESCONF_BIND, DNS_RESCONF_CACHE, DNS_RESCONF_FAMILY, DNS_RESCONF_INET4, DNS_RESCONF_INET6, DNS_RESCONF_OPTIONS, DNS_RESCONF_EDNS0, DNS_RESCONF_NDOTS, DNS_RESCONF_TIMEOUT, DNS_RESCONF_ATTEMPTS, DNS_RESCONF_ROTATE, DNS_RESCONF_RECURSE, DNS_RESCONF_SMART, DNS_RESCONF_TCP, DNS_RESCONF_TCPx, DNS_RESCONF_INTERFACE, DNS_RESCONF_ZERO, DNS_RESCONF_ONE, DNS_RESCONF_ENABLE, DNS_RESCONF_ONLY, DNS_RESCONF_DISABLE, }; /* enum dns_resconf_keyword */ static enum dns_resconf_keyword dns_resconf_keyword(const char *word) { static const char *words[] = { [DNS_RESCONF_NAMESERVER] = "nameserver", [DNS_RESCONF_DOMAIN] = "domain", [DNS_RESCONF_SEARCH] = "search", [DNS_RESCONF_LOOKUP] = "lookup", [DNS_RESCONF_FILE] = "file", [DNS_RESCONF_BIND] = "bind", [DNS_RESCONF_CACHE] = "cache", [DNS_RESCONF_FAMILY] = "family", [DNS_RESCONF_INET4] = "inet4", [DNS_RESCONF_INET6] = "inet6", [DNS_RESCONF_OPTIONS] = "options", [DNS_RESCONF_EDNS0] = "edns0", [DNS_RESCONF_ROTATE] = "rotate", [DNS_RESCONF_RECURSE] = "recurse", [DNS_RESCONF_SMART] = "smart", [DNS_RESCONF_TCP] = "tcp", [DNS_RESCONF_INTERFACE] = "interface", [DNS_RESCONF_ZERO] = "0", [DNS_RESCONF_ONE] = "1", [DNS_RESCONF_ENABLE] = "enable", [DNS_RESCONF_ONLY] = "only", [DNS_RESCONF_DISABLE] = "disable", }; unsigned i; for (i = 0; i < lengthof(words); i++) { if (words[i] && 0 == strcasecmp(words[i], word)) return i; } if (0 == strncasecmp(word, "ndots:", sizeof "ndots:" - 1)) return DNS_RESCONF_NDOTS; if (0 == strncasecmp(word, "timeout:", sizeof "timeout:" - 1)) return DNS_RESCONF_TIMEOUT; if (0 == strncasecmp(word, "attempts:", sizeof "attempts:" - 1)) return DNS_RESCONF_ATTEMPTS; if (0 == strncasecmp(word, "tcp:", sizeof "tcp:" - 1)) return DNS_RESCONF_TCPx; return -1; } /* dns_resconf_keyword() */ /** OpenBSD-style "[1.2.3.4]:53" nameserver syntax */ int dns_resconf_pton(struct sockaddr_storage *ss, const char *src) { struct { char buf[128], *p; } addr = { "", addr.buf }; unsigned short port = 0; int ch, af = AF_INET, error; memset(ss, 0, sizeof *ss); while ((ch = *src++)) { switch (ch) { case ' ': /* FALL THROUGH */ case '\t': break; case '[': break; case ']': while ((ch = *src++)) { if (dns_isdigit(ch)) { port *= 10; port += ch - '0'; } } goto inet; case ':': af = AF_INET6; /* FALL THROUGH */ default: if (addr.p < endof(addr.buf) - 1) *addr.p++ = ch; break; } /* switch() */ } /* while() */ inet: if ((error = dns_pton(af, addr.buf, dns_sa_addr(af, ss, NULL)))) return error; port = (!port)? 53 : port; *dns_sa_port(af, ss) = htons(port); dns_sa_family(ss) = af; return 0; } /* dns_resconf_pton() */ #define dns_resconf_issep(ch) (dns_isspace(ch) || (ch) == ',') #define dns_resconf_iscom(ch) ((ch) == '#' || (ch) == ';') int dns_resconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) { unsigned sa_count = 0; char words[6][DNS_D_MAXNAME + 1]; unsigned wp, wc, i, j, n; int ch, error; rewind(fp); do { memset(words, '\0', sizeof words); wp = 0; wc = 0; while (EOF != (ch = getc(fp)) && ch != '\n') { if (dns_resconf_issep(ch)) { if (wp > 0) { wp = 0; if (++wc >= lengthof(words)) goto skip; } } else if (dns_resconf_iscom(ch)) { skip: do { ch = getc(fp); } while (ch != EOF && ch != '\n'); break; } else if (wp < sizeof words[wc] - 1) { words[wc][wp++] = ch; } else { wp = 0; /* drop word */ goto skip; } } if (wp > 0) wc++; if (wc < 2) continue; switch (dns_resconf_keyword(words[0])) { case DNS_RESCONF_NAMESERVER: if (sa_count >= lengthof(resconf->nameserver)) continue; if ((error = dns_resconf_pton(&resconf->nameserver[sa_count], words[1]))) continue; sa_count++; break; case DNS_RESCONF_DOMAIN: case DNS_RESCONF_SEARCH: memset(resconf->search, '\0', sizeof resconf->search); for (i = 1, j = 0; i < wc && j < lengthof(resconf->search); i++, j++) if (words[i][0] == '.') { /* Ignore invalid search spec. */ j--; } else { dns_d_anchor(resconf->search[j], sizeof resconf->search[j], words[i], strlen(words[i])); } break; case DNS_RESCONF_LOOKUP: for (i = 1, j = 0; i < wc && j < lengthof(resconf->lookup); i++) { switch (dns_resconf_keyword(words[i])) { case DNS_RESCONF_FILE: resconf->lookup[j++] = 'f'; break; case DNS_RESCONF_BIND: resconf->lookup[j++] = 'b'; break; case DNS_RESCONF_CACHE: resconf->lookup[j++] = 'c'; break; default: break; } /* switch() */ } /* for() */ break; case DNS_RESCONF_FAMILY: for (i = 1, j = 0; i < wc && j < lengthof(resconf->family); i++) { switch (dns_resconf_keyword(words[i])) { case DNS_RESCONF_INET4: resconf->family[j++] = AF_INET; break; case DNS_RESCONF_INET6: resconf->family[j++] = AF_INET6; break; default: break; } } break; case DNS_RESCONF_OPTIONS: for (i = 1; i < wc; i++) { switch (dns_resconf_keyword(words[i])) { case DNS_RESCONF_EDNS0: resconf->options.edns0 = 1; break; case DNS_RESCONF_NDOTS: for (j = sizeof "ndots:" - 1, n = 0; dns_isdigit(words[i][j]); j++) { n *= 10; n += words[i][j] - '0'; } /* for() */ resconf->options.ndots = n; break; case DNS_RESCONF_TIMEOUT: for (j = sizeof "timeout:" - 1, n = 0; dns_isdigit(words[i][j]); j++) { n *= 10; n += words[i][j] - '0'; } /* for() */ resconf->options.timeout = n; break; case DNS_RESCONF_ATTEMPTS: for (j = sizeof "attempts:" - 1, n = 0; dns_isdigit(words[i][j]); j++) { n *= 10; n += words[i][j] - '0'; } /* for() */ resconf->options.attempts = n; break; case DNS_RESCONF_ROTATE: resconf->options.rotate = 1; break; case DNS_RESCONF_RECURSE: resconf->options.recurse = 1; break; case DNS_RESCONF_SMART: resconf->options.smart = 1; break; case DNS_RESCONF_TCP: resconf->options.tcp = DNS_RESCONF_TCP_ONLY; break; case DNS_RESCONF_TCPx: switch (dns_resconf_keyword(&words[i][sizeof "tcp:" - 1])) { case DNS_RESCONF_ENABLE: resconf->options.tcp = DNS_RESCONF_TCP_ENABLE; break; case DNS_RESCONF_ONE: case DNS_RESCONF_ONLY: resconf->options.tcp = DNS_RESCONF_TCP_ONLY; break; case DNS_RESCONF_ZERO: case DNS_RESCONF_DISABLE: resconf->options.tcp = DNS_RESCONF_TCP_DISABLE; break; default: break; } /* switch() */ break; default: break; } /* switch() */ } /* for() */ break; case DNS_RESCONF_INTERFACE: for (i = 0, n = 0; dns_isdigit(words[2][i]); i++) { n *= 10; n += words[2][i] - '0'; } dns_resconf_setiface(resconf, words[1], n); break; default: break; } /* switch() */ } while (ch != EOF); return 0; } /* dns_resconf_loadfile() */ int dns_resconf_loadpath(struct dns_resolv_conf *resconf, const char *path) { FILE *fp; int error; if (!(fp = dns_fopen(path, "rt", &error))) return error; error = dns_resconf_loadfile(resconf, fp); fclose(fp); return error; } /* dns_resconf_loadpath() */ struct dns_anyconf { char *token[16]; unsigned count; char buffer[1024], *tp, *cp; }; /* struct dns_anyconf */ static void dns_anyconf_reset(struct dns_anyconf *cf) { cf->count = 0; cf->tp = cf->cp = cf->buffer; } /* dns_anyconf_reset() */ static int dns_anyconf_push(struct dns_anyconf *cf) { if (!(cf->cp < endof(cf->buffer) && cf->count < lengthof(cf->token))) return ENOMEM; *cf->cp++ = '\0'; cf->token[cf->count++] = cf->tp; cf->tp = cf->cp; return 0; } /* dns_anyconf_push() */ static void dns_anyconf_pop(struct dns_anyconf *cf) { if (cf->count > 0) { --cf->count; cf->tp = cf->cp = cf->token[cf->count]; cf->token[cf->count] = 0; } } /* dns_anyconf_pop() */ static int dns_anyconf_addc(struct dns_anyconf *cf, int ch) { if (!(cf->cp < endof(cf->buffer))) return ENOMEM; *cf->cp++ = ch; return 0; } /* dns_anyconf_addc() */ static _Bool dns_anyconf_match(const char *pat, int mc) { _Bool match; int pc; if (*pat == '^') { match = 0; ++pat; } else { match = 1; } while ((pc = *(const unsigned char *)pat++)) { switch (pc) { case '%': if (!(pc = *(const unsigned char *)pat++)) return !match; switch (pc) { case 'a': if (dns_isalpha(mc)) return match; break; case 'd': if (dns_isdigit(mc)) return match; break; case 'w': if (dns_isalnum(mc)) return match; break; case 's': if (dns_isspace(mc)) return match; break; default: if (mc == pc) return match; break; } /* switch() */ break; default: if (mc == pc) return match; break; } /* switch() */ } /* while() */ return !match; } /* dns_anyconf_match() */ static int dns_anyconf_peek(FILE *fp) { int ch; ch = getc(fp); ungetc(ch, fp); return ch; } /* dns_anyconf_peek() */ static size_t dns_anyconf_skip(const char *pat, FILE *fp) { size_t count = 0; int ch; while (EOF != (ch = getc(fp))) { if (dns_anyconf_match(pat, ch)) { count++; continue; } ungetc(ch, fp); break; } return count; } /* dns_anyconf_skip() */ static size_t dns_anyconf_scan(struct dns_anyconf *cf, const char *pat, FILE *fp, int *error) { size_t len; int ch; while (EOF != (ch = getc(fp))) { if (dns_anyconf_match(pat, ch)) { if ((*error = dns_anyconf_addc(cf, ch))) return 0; continue; } else { ungetc(ch, fp); break; } } if ((len = cf->cp - cf->tp)) { if ((*error = dns_anyconf_push(cf))) return 0; return len; } else { *error = 0; return 0; } } /* dns_anyconf_scan() */ DNS_NOTUSED static void dns_anyconf_dump(struct dns_anyconf *cf, FILE *fp) { unsigned i; fprintf(fp, "tokens:"); for (i = 0; i < cf->count; i++) { fprintf(fp, " %s", cf->token[i]); } fputc('\n', fp); } /* dns_anyconf_dump() */ enum dns_nssconf_keyword { DNS_NSSCONF_INVALID = 0, DNS_NSSCONF_HOSTS = 1, DNS_NSSCONF_SUCCESS, DNS_NSSCONF_NOTFOUND, DNS_NSSCONF_UNAVAIL, DNS_NSSCONF_TRYAGAIN, DNS_NSSCONF_CONTINUE, DNS_NSSCONF_RETURN, DNS_NSSCONF_FILES, DNS_NSSCONF_DNS, DNS_NSSCONF_MDNS, DNS_NSSCONF_LAST, }; /* enum dns_nssconf_keyword */ static enum dns_nssconf_keyword dns_nssconf_keyword(const char *word) { static const char *list[] = { [DNS_NSSCONF_HOSTS] = "hosts", [DNS_NSSCONF_SUCCESS] = "success", [DNS_NSSCONF_NOTFOUND] = "notfound", [DNS_NSSCONF_UNAVAIL] = "unavail", [DNS_NSSCONF_TRYAGAIN] = "tryagain", [DNS_NSSCONF_CONTINUE] = "continue", [DNS_NSSCONF_RETURN] = "return", [DNS_NSSCONF_FILES] = "files", [DNS_NSSCONF_DNS] = "dns", [DNS_NSSCONF_MDNS] = "mdns", }; unsigned i; for (i = 1; i < lengthof(list); i++) { if (list[i] && 0 == strcasecmp(list[i], word)) return i; } return DNS_NSSCONF_INVALID; } /* dns_nssconf_keyword() */ static enum dns_nssconf_keyword dns_nssconf_c2k(int ch) { static const char map[] = { ['S'] = DNS_NSSCONF_SUCCESS, ['N'] = DNS_NSSCONF_NOTFOUND, ['U'] = DNS_NSSCONF_UNAVAIL, ['T'] = DNS_NSSCONF_TRYAGAIN, ['C'] = DNS_NSSCONF_CONTINUE, ['R'] = DNS_NSSCONF_RETURN, ['f'] = DNS_NSSCONF_FILES, ['F'] = DNS_NSSCONF_FILES, ['d'] = DNS_NSSCONF_DNS, ['D'] = DNS_NSSCONF_DNS, ['b'] = DNS_NSSCONF_DNS, ['B'] = DNS_NSSCONF_DNS, ['m'] = DNS_NSSCONF_MDNS, ['M'] = DNS_NSSCONF_MDNS, }; return (ch >= 0 && ch < (int)lengthof(map))? map[ch] : DNS_NSSCONF_INVALID; } /* dns_nssconf_c2k() */ DNS_PRAGMA_PUSH DNS_PRAGMA_QUIET static int dns_nssconf_k2c(int k) { static const char map[DNS_NSSCONF_LAST] = { [DNS_NSSCONF_SUCCESS] = 'S', [DNS_NSSCONF_NOTFOUND] = 'N', [DNS_NSSCONF_UNAVAIL] = 'U', [DNS_NSSCONF_TRYAGAIN] = 'T', [DNS_NSSCONF_CONTINUE] = 'C', [DNS_NSSCONF_RETURN] = 'R', [DNS_NSSCONF_FILES] = 'f', [DNS_NSSCONF_DNS] = 'b', [DNS_NSSCONF_MDNS] = 'm', }; return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : '?') : '?'; } /* dns_nssconf_k2c() */ static const char *dns_nssconf_k2s(int k) { static const char *const map[DNS_NSSCONF_LAST] = { [DNS_NSSCONF_SUCCESS] = "SUCCESS", [DNS_NSSCONF_NOTFOUND] = "NOTFOUND", [DNS_NSSCONF_UNAVAIL] = "UNAVAIL", [DNS_NSSCONF_TRYAGAIN] = "TRYAGAIN", [DNS_NSSCONF_CONTINUE] = "continue", [DNS_NSSCONF_RETURN] = "return", [DNS_NSSCONF_FILES] = "files", [DNS_NSSCONF_DNS] = "dns", [DNS_NSSCONF_MDNS] = "mdns", }; return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : "") : ""; } /* dns_nssconf_k2s() */ DNS_PRAGMA_POP int dns_nssconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) { enum dns_nssconf_keyword source, status, action; char lookup[sizeof resconf->lookup] = "", *lp; struct dns_anyconf cf; size_t i; int error; while (!feof(fp) && !ferror(fp)) { dns_anyconf_reset(&cf); dns_anyconf_skip("%s", fp); if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) goto nextent; if (DNS_NSSCONF_HOSTS != dns_nssconf_keyword(cf.token[0])) goto nextent; dns_anyconf_pop(&cf); if (!dns_anyconf_skip(": \t", fp)) goto nextent; *(lp = lookup) = '\0'; while (dns_anyconf_scan(&cf, "%w_", fp, &error)) { dns_anyconf_skip(" \t", fp); if ('[' == dns_anyconf_peek(fp)) { dns_anyconf_skip("[! \t", fp); while (dns_anyconf_scan(&cf, "%w_", fp, &error)) { dns_anyconf_skip("= \t", fp); if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) { dns_anyconf_pop(&cf); /* discard status */ dns_anyconf_skip("^#;]\n", fp); /* skip to end of criteria */ break; } dns_anyconf_skip(" \t", fp); } dns_anyconf_skip("] \t", fp); } if ((size_t)(endof(lookup) - lp) < cf.count + 1) /* +1 for '\0' */ goto nextsrc; source = dns_nssconf_keyword(cf.token[0]); switch (source) { case DNS_NSSCONF_DNS: case DNS_NSSCONF_MDNS: case DNS_NSSCONF_FILES: *lp++ = dns_nssconf_k2c(source); break; default: goto nextsrc; } for (i = 1; i + 1 < cf.count; i += 2) { status = dns_nssconf_keyword(cf.token[i]); action = dns_nssconf_keyword(cf.token[i + 1]); switch (status) { case DNS_NSSCONF_SUCCESS: case DNS_NSSCONF_NOTFOUND: case DNS_NSSCONF_UNAVAIL: case DNS_NSSCONF_TRYAGAIN: *lp++ = dns_nssconf_k2c(status); break; default: continue; } switch (action) { case DNS_NSSCONF_CONTINUE: case DNS_NSSCONF_RETURN: break; default: action = (status == DNS_NSSCONF_SUCCESS) ? DNS_NSSCONF_RETURN : DNS_NSSCONF_CONTINUE; break; } *lp++ = dns_nssconf_k2c(action); } nextsrc: *lp = '\0'; dns_anyconf_reset(&cf); } nextent: dns_anyconf_skip("^\n", fp); } if (*lookup) strncpy(resconf->lookup, lookup, sizeof resconf->lookup); return 0; } /* dns_nssconf_loadfile() */ int dns_nssconf_loadpath(struct dns_resolv_conf *resconf, const char *path) { FILE *fp; int error; if (!(fp = dns_fopen(path, "rt", &error))) return error; error = dns_nssconf_loadfile(resconf, fp); fclose(fp); return error; } /* dns_nssconf_loadpath() */ struct dns_nssconf_source { enum dns_nssconf_keyword source, success, notfound, unavail, tryagain; }; /* struct dns_nssconf_source */ typedef unsigned dns_nssconf_i; static inline int dns_nssconf_peek(const struct dns_resolv_conf *resconf, dns_nssconf_i state) { return (state < lengthof(resconf->lookup) && resconf->lookup[state])? resconf->lookup[state] : 0; } /* dns_nssconf_peek() */ static _Bool dns_nssconf_next(struct dns_nssconf_source *src, const struct dns_resolv_conf *resconf, dns_nssconf_i *state) { int source, status, action; src->source = DNS_NSSCONF_INVALID; src->success = DNS_NSSCONF_RETURN; src->notfound = DNS_NSSCONF_CONTINUE; src->unavail = DNS_NSSCONF_CONTINUE; src->tryagain = DNS_NSSCONF_CONTINUE; while ((source = dns_nssconf_peek(resconf, *state))) { source = dns_nssconf_c2k(source); ++*state; switch (source) { case DNS_NSSCONF_FILES: case DNS_NSSCONF_DNS: case DNS_NSSCONF_MDNS: src->source = source; break; default: continue; } while ((status = dns_nssconf_peek(resconf, *state)) && (action = dns_nssconf_peek(resconf, *state + 1))) { status = dns_nssconf_c2k(status); action = dns_nssconf_c2k(action); switch (action) { case DNS_NSSCONF_RETURN: case DNS_NSSCONF_CONTINUE: break; default: goto done; } switch (status) { case DNS_NSSCONF_SUCCESS: src->success = action; break; case DNS_NSSCONF_NOTFOUND: src->notfound = action; break; case DNS_NSSCONF_UNAVAIL: src->unavail = action; break; case DNS_NSSCONF_TRYAGAIN: src->tryagain = action; break; default: goto done; } *state += 2; } break; } done: return src->source != DNS_NSSCONF_INVALID; } /* dns_nssconf_next() */ static int dns_nssconf_dump_status(int status, int action, unsigned *count, FILE *fp) { switch (status) { case DNS_NSSCONF_SUCCESS: if (action == DNS_NSSCONF_RETURN) return 0; break; default: if (action == DNS_NSSCONF_CONTINUE) return 0; break; } fputc(' ', fp); if (!*count) fputc('[', fp); fprintf(fp, "%s=%s", dns_nssconf_k2s(status), dns_nssconf_k2s(action)); ++*count; return 0; } /* dns_nssconf_dump_status() */ int dns_nssconf_dump(struct dns_resolv_conf *resconf, FILE *fp) { struct dns_nssconf_source src; dns_nssconf_i i = 0; fputs("hosts:", fp); while (dns_nssconf_next(&src, resconf, &i)) { unsigned n = 0; fprintf(fp, " %s", dns_nssconf_k2s(src.source)); dns_nssconf_dump_status(DNS_NSSCONF_SUCCESS, src.success, &n, fp); dns_nssconf_dump_status(DNS_NSSCONF_NOTFOUND, src.notfound, &n, fp); dns_nssconf_dump_status(DNS_NSSCONF_UNAVAIL, src.unavail, &n, fp); dns_nssconf_dump_status(DNS_NSSCONF_TRYAGAIN, src.tryagain, &n, fp); if (n) fputc(']', fp); } fputc('\n', fp); return 0; } /* dns_nssconf_dump() */ int dns_resconf_setiface(struct dns_resolv_conf *resconf, const char *addr, unsigned short port) { int af = (strchr(addr, ':'))? AF_INET6 : AF_INET; int error; memset(&resconf->iface, 0, sizeof (struct sockaddr_storage)); if ((error = dns_pton(af, addr, dns_sa_addr(af, &resconf->iface, NULL)))) return error; *dns_sa_port(af, &resconf->iface) = htons(port); resconf->iface.ss_family = af; return 0; } /* dns_resconf_setiface() */ #define DNS_SM_RESTORE \ do { \ pc = 0xff & (*state >> 0); \ srchi = 0xff & (*state >> 8); \ ndots = 0xff & (*state >> 16); \ } while (0) #define DNS_SM_SAVE \ do { \ *state = ((0xff & pc) << 0) \ | ((0xff & srchi) << 8) \ | ((0xff & ndots) << 16); \ } while (0) size_t dns_resconf_search(void *dst, size_t lim, const void *qname, size_t qlen, struct dns_resolv_conf *resconf, dns_resconf_i_t *state) { unsigned pc, srchi, ndots, len; DNS_SM_ENTER; /* if FQDN then return as-is and finish */ if (dns_d_isanchored(qname, qlen)) { len = dns_d_anchor(dst, lim, qname, qlen); DNS_SM_YIELD(len); DNS_SM_EXIT; } ndots = dns_d_ndots(qname, qlen); if (ndots >= resconf->options.ndots) { len = dns_d_anchor(dst, lim, qname, qlen); DNS_SM_YIELD(len); } while (srchi < lengthof(resconf->search) && resconf->search[srchi][0]) { struct dns_buf buf = DNS_B_INTO(dst, lim); const char *dn = resconf->search[srchi++]; dns_b_put(&buf, qname, qlen); dns_b_putc(&buf, '.'); dns_b_puts(&buf, dn); if (!dns_d_isanchored(dn, strlen(dn))) dns_b_putc(&buf, '.'); len = dns_b_strllen(&buf); DNS_SM_YIELD(len); } if (ndots < resconf->options.ndots) { len = dns_d_anchor(dst, lim, qname, qlen); DNS_SM_YIELD(len); } DNS_SM_LEAVE; return dns_strlcpy(dst, "", lim); } /* dns_resconf_search() */ #undef DNS_SM_SAVE #undef DNS_SM_RESTORE int dns_resconf_dump(struct dns_resolv_conf *resconf, FILE *fp) { unsigned i; int af; for (i = 0; i < lengthof(resconf->nameserver) && (af = resconf->nameserver[i].ss_family) != AF_UNSPEC; i++) { char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]"; unsigned short port; dns_inet_ntop(af, dns_sa_addr(af, &resconf->nameserver[i], NULL), addr, sizeof addr); port = ntohs(*dns_sa_port(af, &resconf->nameserver[i])); if (port == 53) fprintf(fp, "nameserver %s\n", addr); else fprintf(fp, "nameserver [%s]:%hu\n", addr, port); } fprintf(fp, "search"); for (i = 0; i < lengthof(resconf->search) && resconf->search[i][0]; i++) fprintf(fp, " %s", resconf->search[i]); fputc('\n', fp); fputs("; ", fp); dns_nssconf_dump(resconf, fp); fprintf(fp, "lookup"); for (i = 0; i < lengthof(resconf->lookup) && resconf->lookup[i]; i++) { switch (resconf->lookup[i]) { case 'b': fprintf(fp, " bind"); break; case 'f': fprintf(fp, " file"); break; case 'c': fprintf(fp, " cache"); break; } } fputc('\n', fp); fprintf(fp, "options ndots:%u timeout:%u attempts:%u", resconf->options.ndots, resconf->options.timeout, resconf->options.attempts); if (resconf->options.edns0) fprintf(fp, " edns0"); if (resconf->options.rotate) fprintf(fp, " rotate"); if (resconf->options.recurse) fprintf(fp, " recurse"); if (resconf->options.smart) fprintf(fp, " smart"); switch (resconf->options.tcp) { case DNS_RESCONF_TCP_ENABLE: break; case DNS_RESCONF_TCP_ONLY: fprintf(fp, " tcp"); break; case DNS_RESCONF_TCP_SOCKS: fprintf(fp, " tcp:socks"); break; case DNS_RESCONF_TCP_DISABLE: fprintf(fp, " tcp:disable"); break; } fputc('\n', fp); if ((af = resconf->iface.ss_family) != AF_UNSPEC) { char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]"; dns_inet_ntop(af, dns_sa_addr(af, &resconf->iface, NULL), addr, sizeof addr); fprintf(fp, "interface %s %hu\n", addr, ntohs(*dns_sa_port(af, &resconf->iface))); } return 0; } /* dns_resconf_dump() */ /* * H I N T S E R V E R R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ struct dns_hints_soa { unsigned char zone[DNS_D_MAXNAME + 1]; struct { struct sockaddr_storage ss; unsigned priority; } addrs[16]; unsigned count; struct dns_hints_soa *next; }; /* struct dns_hints_soa */ struct dns_hints { dns_atomic_t refcount; struct dns_hints_soa *head; }; /* struct dns_hints */ struct dns_hints *dns_hints_open(struct dns_resolv_conf *resconf, int *error) { static const struct dns_hints H_initializer; struct dns_hints *H; (void)resconf; if (!(H = malloc(sizeof *H))) goto syerr; *H = H_initializer; dns_hints_acquire(H); return H; syerr: *error = dns_syerr(); free(H); return 0; } /* dns_hints_open() */ void dns_hints_close(struct dns_hints *H) { struct dns_hints_soa *soa, *nxt; if (!H || 1 != dns_hints_release(H)) return /* void */; for (soa = H->head; soa; soa = nxt) { nxt = soa->next; free(soa); } free(H); return /* void */; } /* dns_hints_close() */ dns_refcount_t dns_hints_acquire(struct dns_hints *H) { return dns_atomic_fetch_add(&H->refcount); } /* dns_hints_acquire() */ dns_refcount_t dns_hints_release(struct dns_hints *H) { return dns_atomic_fetch_sub(&H->refcount); } /* dns_hints_release() */ struct dns_hints *dns_hints_mortal(struct dns_hints *hints) { if (hints) dns_hints_release(hints); return hints; } /* dns_hints_mortal() */ struct dns_hints *dns_hints_local(struct dns_resolv_conf *resconf, int *error_) { struct dns_hints *hints = 0; int error; if (resconf) dns_resconf_acquire(resconf); else if (!(resconf = dns_resconf_local(&error))) goto error; if (!(hints = dns_hints_open(resconf, &error))) goto error; error = 0; if (0 == dns_hints_insert_resconf(hints, ".", resconf, &error) && error) goto error; dns_resconf_close(resconf); return hints; error: *error_ = error; dns_resconf_close(resconf); dns_hints_close(hints); return 0; } /* dns_hints_local() */ struct dns_hints *dns_hints_root(struct dns_resolv_conf *resconf, int *error_) { static const struct { int af; char addr[INET6_ADDRSTRLEN]; } root_hints[] = { { AF_INET, "198.41.0.4" }, /* A.ROOT-SERVERS.NET. */ { AF_INET6, "2001:503:ba3e::2:30" }, /* A.ROOT-SERVERS.NET. */ { AF_INET, "192.228.79.201" }, /* B.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:84::b" }, /* B.ROOT-SERVERS.NET. */ { AF_INET, "192.33.4.12" }, /* C.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:2::c" }, /* C.ROOT-SERVERS.NET. */ { AF_INET, "199.7.91.13" }, /* D.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:2d::d" }, /* D.ROOT-SERVERS.NET. */ { AF_INET, "192.203.230.10" }, /* E.ROOT-SERVERS.NET. */ { AF_INET, "192.5.5.241" }, /* F.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:2f::f" }, /* F.ROOT-SERVERS.NET. */ { AF_INET, "192.112.36.4" }, /* G.ROOT-SERVERS.NET. */ { AF_INET, "128.63.2.53" }, /* H.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:1::803f:235" }, /* H.ROOT-SERVERS.NET. */ { AF_INET, "192.36.148.17" }, /* I.ROOT-SERVERS.NET. */ { AF_INET6, "2001:7FE::53" }, /* I.ROOT-SERVERS.NET. */ { AF_INET, "192.58.128.30" }, /* J.ROOT-SERVERS.NET. */ { AF_INET6, "2001:503:c27::2:30" }, /* J.ROOT-SERVERS.NET. */ { AF_INET, "193.0.14.129" }, /* K.ROOT-SERVERS.NET. */ { AF_INET6, "2001:7FD::1" }, /* K.ROOT-SERVERS.NET. */ { AF_INET, "199.7.83.42" }, /* L.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:3::42" }, /* L.ROOT-SERVERS.NET. */ { AF_INET, "202.12.27.33" }, /* M.ROOT-SERVERS.NET. */ { AF_INET6, "2001:DC3::35" }, /* M.ROOT-SERVERS.NET. */ }; struct dns_hints *hints = 0; struct sockaddr_storage ss; unsigned i; int error, af; if (!(hints = dns_hints_open(resconf, &error))) goto error; for (i = 0; i < lengthof(root_hints); i++) { af = root_hints[i].af; memset(&ss, 0, sizeof ss); if ((error = dns_pton(af, root_hints[i].addr, dns_sa_addr(af, &ss, NULL)))) goto error; *dns_sa_port(af, &ss) = htons(53); ss.ss_family = af; if ((error = dns_hints_insert(hints, ".", (struct sockaddr *)&ss, 1))) goto error; } return hints; error: *error_ = error; dns_hints_close(hints); return 0; } /* dns_hints_root() */ static struct dns_hints_soa *dns_hints_fetch(struct dns_hints *H, const char *zone) { struct dns_hints_soa *soa; for (soa = H->head; soa; soa = soa->next) { if (0 == strcasecmp(zone, (char *)soa->zone)) return soa; } return 0; } /* dns_hints_fetch() */ int dns_hints_insert(struct dns_hints *H, const char *zone, const struct sockaddr *sa, unsigned priority) { static const struct dns_hints_soa soa_initializer; struct dns_hints_soa *soa; unsigned i; if (!(soa = dns_hints_fetch(H, zone))) { if (!(soa = malloc(sizeof *soa))) return dns_syerr(); *soa = soa_initializer; dns_strlcpy((char *)soa->zone, zone, sizeof soa->zone); soa->next = H->head; H->head = soa; } i = soa->count % lengthof(soa->addrs); memcpy(&soa->addrs[i].ss, sa, dns_sa_len(sa)); soa->addrs[i].priority = DNS_PP_MAX(1, priority); if (soa->count < lengthof(soa->addrs)) soa->count++; return 0; } /* dns_hints_insert() */ static _Bool dns_hints_isinaddr_any(const void *sa) { struct in_addr *addr; if (dns_sa_family(sa) != AF_INET) return 0; addr = dns_sa_addr(AF_INET, sa, NULL); return addr->s_addr == htonl(INADDR_ANY); } unsigned dns_hints_insert_resconf(struct dns_hints *H, const char *zone, const struct dns_resolv_conf *resconf, int *error_) { unsigned i, n, p; int error; for (i = 0, n = 0, p = 1; i < lengthof(resconf->nameserver) && resconf->nameserver[i].ss_family != AF_UNSPEC; i++, n++) { union { struct sockaddr_in sin; } tmp; struct sockaddr *ns; /* * dns_resconf_open initializes nameserver[0] to INADDR_ANY. * * Traditionally the semantics of 0.0.0.0 meant the default * interface, which evolved to mean the loopback interface. * See comment block preceding resolv/res_init.c:res_init in * glibc 2.23. As of 2.23, glibc no longer translates * 0.0.0.0 despite the code comment, but it does default to * 127.0.0.1 when no nameservers are present. * * BIND9 as of 9.10.3 still translates 0.0.0.0 to 127.0.0.1. * See lib/lwres/lwconfig.c:lwres_create_addr and the * convert_zero flag. 127.0.0.1 is also the default when no * nameservers are present. */ if (dns_hints_isinaddr_any(&resconf->nameserver[i])) { memcpy(&tmp.sin, &resconf->nameserver[i], sizeof tmp.sin); tmp.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); ns = (struct sockaddr *)&tmp.sin; } else { ns = (struct sockaddr *)&resconf->nameserver[i]; } if ((error = dns_hints_insert(H, zone, ns, p))) goto error; p += !resconf->options.rotate; } return n; error: *error_ = error; return n; } /* dns_hints_insert_resconf() */ static int dns_hints_i_cmp(unsigned a, unsigned b, struct dns_hints_i *i, struct dns_hints_soa *soa) { int cmp; if ((cmp = soa->addrs[a].priority - soa->addrs[b].priority)) return cmp; return dns_k_shuffle16(a, i->state.seed) - dns_k_shuffle16(b, i->state.seed); } /* dns_hints_i_cmp() */ static unsigned dns_hints_i_start(struct dns_hints_i *i, struct dns_hints_soa *soa) { unsigned p0, p; p0 = 0; for (p = 1; p < soa->count; p++) { if (dns_hints_i_cmp(p, p0, i, soa) < 0) p0 = p; } return p0; } /* dns_hints_i_start() */ static unsigned dns_hints_i_skip(unsigned p0, struct dns_hints_i *i, struct dns_hints_soa *soa) { unsigned pZ, p; for (pZ = 0; pZ < soa->count; pZ++) { if (dns_hints_i_cmp(pZ, p0, i, soa) > 0) goto cont; } return soa->count; cont: for (p = pZ + 1; p < soa->count; p++) { if (dns_hints_i_cmp(p, p0, i, soa) <= 0) continue; if (dns_hints_i_cmp(p, pZ, i, soa) >= 0) continue; pZ = p; } return pZ; } /* dns_hints_i_skip() */ static struct dns_hints_i *dns_hints_i_init(struct dns_hints_i *i, struct dns_hints *hints) { static const struct dns_hints_i i_initializer; struct dns_hints_soa *soa; i->state = i_initializer.state; do { i->state.seed = dns_random(); } while (0 == i->state.seed); if ((soa = dns_hints_fetch(hints, i->zone))) { i->state.next = dns_hints_i_start(i, soa); } return i; } /* dns_hints_i_init() */ unsigned dns_hints_grep(struct sockaddr **sa, socklen_t *sa_len, unsigned lim, struct dns_hints_i *i, struct dns_hints *H) { struct dns_hints_soa *soa; unsigned n; if (!(soa = dns_hints_fetch(H, i->zone))) return 0; n = 0; while (i->state.next < soa->count && n < lim) { *sa = (struct sockaddr *)&soa->addrs[i->state.next].ss; *sa_len = dns_sa_len(*sa); sa++; sa_len++; n++; i->state.next = dns_hints_i_skip(i->state.next, i, soa); } return n; } /* dns_hints_grep() */ struct dns_packet *dns_hints_query(struct dns_hints *hints, struct dns_packet *Q, int *error_) { union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 }; struct dns_packet *A, *P; struct dns_rr rr; char zone[DNS_D_MAXNAME + 1]; size_t zlen; struct dns_hints_i i; struct sockaddr *sa; socklen_t slen; int error; struct dns_rr_i I_instance = { 0 }; I_instance.section = DNS_S_QUESTION; if (!dns_rr_grep(&rr, 1, &I_instance, Q, &error)) goto error; if (!(zlen = dns_d_expand(zone, sizeof zone, rr.dn.p, Q, &error))) goto error; else if (zlen >= sizeof zone) goto toolong; P = dns_p_init(&P_instance.p, 512); dns_header(P)->qr = 1; if ((error = dns_rr_copy(P, &rr, Q))) goto error; if ((error = dns_p_push(P, DNS_S_AUTHORITY, ".", strlen("."), DNS_T_NS, DNS_C_IN, 0, "hints.local."))) goto error; do { i.zone = zone; dns_hints_i_init(&i, hints); while (dns_hints_grep(&sa, &slen, 1, &i, hints)) { int af = sa->sa_family; int rtype = (af == AF_INET6)? DNS_T_AAAA : DNS_T_A; if ((error = dns_p_push(P, DNS_S_ADDITIONAL, "hints.local.", strlen("hints.local."), rtype, DNS_C_IN, 0, dns_sa_addr(af, sa, NULL)))) goto error; } } while ((zlen = dns_d_cleave(zone, sizeof zone, zone, zlen))); if (!(A = dns_p_copy(dns_p_make(P->end, &error), P))) goto error; return A; toolong: error = DNS_EILLEGAL; error: *error_ = error; return 0; } /* dns_hints_query() */ /** ugly hack to support specifying ports other than 53 in resolv.conf. */ static unsigned short dns_hints_port(struct dns_hints *hints, int af, void *addr) { struct dns_hints_soa *soa; void *addrsoa; socklen_t addrlen; unsigned short port; unsigned i; for (soa = hints->head; soa; soa = soa->next) { for (i = 0; i < soa->count; i++) { if (af != soa->addrs[i].ss.ss_family) continue; if (!(addrsoa = dns_sa_addr(af, &soa->addrs[i].ss, &addrlen))) continue; if (memcmp(addr, addrsoa, addrlen)) continue; port = *dns_sa_port(af, &soa->addrs[i].ss); return (port)? port : htons(53); } } return htons(53); } /* dns_hints_port() */ int dns_hints_dump(struct dns_hints *hints, FILE *fp) { struct dns_hints_soa *soa; char addr[INET6_ADDRSTRLEN]; unsigned i; int af, error; for (soa = hints->head; soa; soa = soa->next) { fprintf(fp, "ZONE \"%s\"\n", soa->zone); for (i = 0; i < soa->count; i++) { af = soa->addrs[i].ss.ss_family; if ((error = dns_ntop(af, dns_sa_addr(af, &soa->addrs[i].ss, NULL), addr, sizeof addr))) return error; fprintf(fp, "\t(%d) [%s]:%hu\n", (int)soa->addrs[i].priority, addr, ntohs(*dns_sa_port(af, &soa->addrs[i].ss))); } } return 0; } /* dns_hints_dump() */ /* * C A C H E R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ static dns_refcount_t dns_cache_acquire(struct dns_cache *cache) { return dns_atomic_fetch_add(&cache->_.refcount); } /* dns_cache_acquire() */ static dns_refcount_t dns_cache_release(struct dns_cache *cache) { return dns_atomic_fetch_sub(&cache->_.refcount); } /* dns_cache_release() */ static struct dns_packet *dns_cache_query(struct dns_packet *query, struct dns_cache *cache, int *error) { (void)query; (void)cache; (void)error; return NULL; } /* dns_cache_query() */ static int dns_cache_submit(struct dns_packet *query, struct dns_cache *cache) { (void)query; (void)cache; return 0; } /* dns_cache_submit() */ static int dns_cache_check(struct dns_cache *cache) { (void)cache; return 0; } /* dns_cache_check() */ static struct dns_packet *dns_cache_fetch(struct dns_cache *cache, int *error) { (void)cache; (void)error; return NULL; } /* dns_cache_fetch() */ static int dns_cache_pollfd(struct dns_cache *cache) { (void)cache; return -1; } /* dns_cache_pollfd() */ static short dns_cache_events(struct dns_cache *cache) { (void)cache; return 0; } /* dns_cache_events() */ static void dns_cache_clear(struct dns_cache *cache) { (void)cache; return; } /* dns_cache_clear() */ struct dns_cache *dns_cache_init(struct dns_cache *cache) { static const struct dns_cache c_init = { .acquire = &dns_cache_acquire, .release = &dns_cache_release, .query = &dns_cache_query, .submit = &dns_cache_submit, .check = &dns_cache_check, .fetch = &dns_cache_fetch, .pollfd = &dns_cache_pollfd, .events = &dns_cache_events, .clear = &dns_cache_clear, ._ = { .refcount = 1, }, }; *cache = c_init; return cache; } /* dns_cache_init() */ void dns_cache_close(struct dns_cache *cache) { if (cache) cache->release(cache); } /* dns_cache_close() */ /* * S O C K E T R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ static void dns_socketclose(int *fd, const struct dns_options *opts) { if (opts && opts->closefd.cb) opts->closefd.cb(fd, opts->closefd.arg); if (*fd != -1) { #if _WIN32 closesocket(*fd); #else close(*fd); #endif *fd = -1; } } /* dns_socketclose() */ #ifndef HAVE_IOCTLSOCKET #define HAVE_IOCTLSOCKET (_WIN32 || _WIN64) #endif #ifndef HAVE_SOCK_CLOEXEC #ifdef SOCK_CLOEXEC #define HAVE_SOCK_CLOEXEC 1 #else #define HAVE_SOCK_CLOEXEC 0 #endif #endif #ifndef HAVE_SOCK_NONBLOCK #ifdef SOCK_NONBLOCK #define HAVE_SOCK_NONBLOCK 1 #else #define HAVE_SOCK_NONBLOCK 0 #endif #endif #define DNS_SO_MAXTRY 7 static int dns_socket(struct sockaddr *local, int type, int *error_) { int fd = -1, flags, error; #if defined FIONBIO unsigned long opt; #endif flags = 0; #if HAVE_SOCK_CLOEXEC flags |= SOCK_CLOEXEC; #endif #if HAVE_SOCK_NONBLOCK flags |= SOCK_NONBLOCK; #endif if (-1 == (fd = socket(local->sa_family, type|flags, 0))) goto soerr; #if defined F_SETFD && !HAVE_SOCK_CLOEXEC if (-1 == fcntl(fd, F_SETFD, 1)) goto syerr; #endif #if defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK if (-1 == (flags = fcntl(fd, F_GETFL))) goto syerr; if (-1 == fcntl(fd, F_SETFL, flags | O_NONBLOCK)) goto syerr; #elif defined FIONBIO && HAVE_IOCTLSOCKET opt = 1; if (0 != ioctlsocket(fd, FIONBIO, &opt)) goto soerr; #endif #if defined SO_NOSIGPIPE if (type != SOCK_DGRAM) { const int v = 1; if (0 != setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &v, sizeof (int))) goto soerr; } #endif if (local->sa_family != AF_INET && local->sa_family != AF_INET6) return fd; if (type != SOCK_DGRAM) return fd; #define LEAVE_SELECTION_OF_PORT_TO_KERNEL #if !defined(LEAVE_SELECTION_OF_PORT_TO_KERNEL) /* * FreeBSD, Linux, OpenBSD, OS X, and Solaris use random ports by * default. Though the ephemeral range is quite small on OS X * (49152-65535 on 10.10) and Linux (32768-60999 on 4.4.0, Ubuntu * Xenial). See also RFC 6056. * * TODO: Optionally rely on the kernel to select a random port. */ if (*dns_sa_port(local->sa_family, local) == 0) { struct sockaddr_storage tmp; unsigned i, port; memcpy(&tmp, local, dns_sa_len(local)); for (i = 0; i < DNS_SO_MAXTRY; i++) { port = 1025 + (dns_random() % 64510); *dns_sa_port(tmp.ss_family, &tmp) = htons(port); if (0 == bind(fd, (struct sockaddr *)&tmp, dns_sa_len(&tmp))) return fd; } /* NB: continue to next bind statement */ } #endif if (0 == bind(fd, local, dns_sa_len(local))) return fd; /* FALL THROUGH */ soerr: error = dns_soerr(); goto error; #if (defined F_SETFD && !HAVE_SOCK_CLOEXEC) || (defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK) syerr: error = dns_syerr(); goto error; #endif error: *error_ = error; dns_socketclose(&fd, NULL); return -1; } /* dns_socket() */ enum { DNS_SO_UDP_INIT = 1, DNS_SO_UDP_CONN, DNS_SO_UDP_SEND, DNS_SO_UDP_RECV, DNS_SO_UDP_DONE, DNS_SO_TCP_INIT, DNS_SO_TCP_CONN, DNS_SO_TCP_SEND, DNS_SO_TCP_RECV, DNS_SO_TCP_DONE, DNS_SO_SOCKS_INIT, DNS_SO_SOCKS_CONN, DNS_SO_SOCKS_HELLO_SEND, DNS_SO_SOCKS_HELLO_RECV, DNS_SO_SOCKS_AUTH_SEND, DNS_SO_SOCKS_AUTH_RECV, DNS_SO_SOCKS_REQUEST_PREPARE, DNS_SO_SOCKS_REQUEST_SEND, DNS_SO_SOCKS_REQUEST_RECV, DNS_SO_SOCKS_REQUEST_RECV_V6, DNS_SO_SOCKS_HANDSHAKE_DONE, }; struct dns_socket { struct dns_options opts; int udp; int tcp; int *old; unsigned onum, olim; int type; struct sockaddr_storage local, remote; struct dns_k_permutor qids; struct dns_stat stat; struct dns_trace *trace; /* * NOTE: dns_so_reset() zeroes everything from here down. */ int state; unsigned short qid; char qname[DNS_D_MAXNAME + 1]; size_t qlen; enum dns_type qtype; enum dns_class qclass; struct dns_packet *query; size_t qout; /* During a SOCKS handshake the query is temporarily stored * here. */ struct dns_packet *query_backup; struct dns_clock elapsed; struct dns_packet *answer; size_t alen, apos; }; /* struct dns_socket */ /* * NOTE: Actual closure delayed so that kqueue(2) and epoll(2) callers have * a chance to recognize a state change after installing a persistent event * and where sequential descriptors with the same integer value returned * from _pollfd() would be ambiguous. See dns_so_closefds(). */ static int dns_so_closefd(struct dns_socket *so, int *fd) { int error; if (*fd == -1) return 0; if (so->opts.closefd.cb) { if ((error = so->opts.closefd.cb(fd, so->opts.closefd.arg))) { return error; } else if (*fd == -1) return 0; } if (!(so->onum < so->olim)) { unsigned olim = DNS_PP_MAX(4, so->olim * 2); void *old; if (!(old = realloc(so->old, sizeof so->old[0] * olim))) return dns_syerr(); so->old = old; so->olim = olim; } so->old[so->onum++] = *fd; *fd = -1; return 0; } /* dns_so_closefd() */ #define DNS_SO_CLOSE_UDP 0x01 #define DNS_SO_CLOSE_TCP 0x02 #define DNS_SO_CLOSE_OLD 0x04 #define DNS_SO_CLOSE_ALL (DNS_SO_CLOSE_UDP|DNS_SO_CLOSE_TCP|DNS_SO_CLOSE_OLD) static void dns_so_closefds(struct dns_socket *so, int which) { if (DNS_SO_CLOSE_UDP & which) dns_socketclose(&so->udp, &so->opts); if (DNS_SO_CLOSE_TCP & which) dns_socketclose(&so->tcp, &so->opts); if (DNS_SO_CLOSE_OLD & which) { unsigned i; for (i = 0; i < so->onum; i++) dns_socketclose(&so->old[i], &so->opts); so->onum = 0; free(so->old); so->old = 0; so->olim = 0; } } /* dns_so_closefds() */ static void dns_so_destroy(struct dns_socket *); static struct dns_socket *dns_so_init(struct dns_socket *so, const struct sockaddr *local, int type, const struct dns_options *opts, int *error) { static const struct dns_socket so_initializer = { .opts = DNS_OPTS_INITIALIZER, .udp = -1, .tcp = -1, }; *so = so_initializer; so->type = type; if (opts) so->opts = *opts; if (local) memcpy(&so->local, local, dns_sa_len(local)); if (-1 == (so->udp = dns_socket((struct sockaddr *)&so->local, SOCK_DGRAM, error))) goto error; dns_k_permutor_init(&so->qids, 1, 65535); return so; error: dns_so_destroy(so); return 0; } /* dns_so_init() */ struct dns_socket *dns_so_open(const struct sockaddr *local, int type, const struct dns_options *opts, int *error) { struct dns_socket *so; if (!(so = malloc(sizeof *so))) goto syerr; if (!dns_so_init(so, local, type, opts, error)) goto error; return so; syerr: *error = dns_syerr(); error: dns_so_close(so); return 0; } /* dns_so_open() */ static void dns_so_destroy(struct dns_socket *so) { dns_so_reset(so); dns_so_closefds(so, DNS_SO_CLOSE_ALL); dns_trace_close(so->trace); } /* dns_so_destroy() */ void dns_so_close(struct dns_socket *so) { if (!so) return; dns_so_destroy(so); free(so); } /* dns_so_close() */ void dns_so_reset(struct dns_socket *so) { dns_p_setptr(&so->answer, NULL); memset(&so->state, '\0', sizeof *so - offsetof(struct dns_socket, state)); } /* dns_so_reset() */ unsigned short dns_so_mkqid(struct dns_socket *so) { return dns_k_permutor_step(&so->qids); } /* dns_so_mkqid() */ #define DNS_SO_MINBUF 768 static int dns_so_newanswer(struct dns_socket *so, size_t len) { size_t size = offsetof(struct dns_packet, data) + DNS_PP_MAX(len, DNS_SO_MINBUF); void *p; if (!(p = realloc(so->answer, size))) return dns_syerr(); so->answer = dns_p_init(p, size); return 0; } /* dns_so_newanswer() */ int dns_so_submit(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host) { struct dns_rr rr; int error = DNS_EUNKNOWN; dns_so_reset(so); if ((error = dns_rr_parse(&rr, 12, Q))) goto error; if (!(so->qlen = dns_d_expand(so->qname, sizeof so->qname, rr.dn.p, Q, &error))) goto error; /* * NOTE: Don't bail if expansion is too long; caller may be * intentionally sending long names. However, we won't be able to * verify it on return. */ so->qtype = rr.type; so->qclass = rr.class; if ((error = dns_so_newanswer(so, (Q->memo.opt.maxudp)? Q->memo.opt.maxudp : DNS_SO_MINBUF))) goto syerr; memcpy(&so->remote, host, dns_sa_len(host)); so->query = Q; so->qout = 0; dns_begin(&so->elapsed); if (dns_header(so->query)->qid == 0) dns_header(so->query)->qid = dns_so_mkqid(so); so->qid = dns_header(so->query)->qid; so->state = (so->opts.socks_host && so->opts.socks_host->ss_family) ? DNS_SO_SOCKS_INIT : (so->type == SOCK_STREAM)? DNS_SO_TCP_INIT : DNS_SO_UDP_INIT; so->stat.queries++; dns_trace_so_submit(so->trace, Q, host, 0); return 0; syerr: error = dns_syerr(); error: dns_so_reset(so); dns_trace_so_submit(so->trace, Q, host, error); return error; } /* dns_so_submit() */ static int dns_so_verify(struct dns_socket *so, struct dns_packet *P) { char qname[DNS_D_MAXNAME + 1]; size_t qlen; struct dns_rr rr; int error = -1; if (P->end < 12) goto reject; if (so->qid != dns_header(P)->qid) goto reject; if (!dns_p_count(P, DNS_S_QD)) goto reject; if (0 != dns_rr_parse(&rr, 12, P)) goto reject; if (rr.type != so->qtype || rr.class != so->qclass) goto reject; if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, P, &error))) goto error; else if (qlen >= sizeof qname || qlen != so->qlen) goto reject; if (0 != strcasecmp(so->qname, qname)) goto reject; dns_trace_so_verify(so->trace, P, 0); return 0; reject: error = DNS_EVERIFY; error: DNS_SHOW(P, "rejecting packet (%s)", dns_strerror(error)); dns_trace_so_verify(so->trace, P, error); return error; } /* dns_so_verify() */ static _Bool dns_so_tcp_keep(struct dns_socket *so) { struct sockaddr_storage remote; socklen_t l = sizeof remote; if (so->tcp == -1) return 0; if (0 != getpeername(so->tcp, (struct sockaddr *)&remote, &l)) return 0; return 0 == dns_sa_cmp(&remote, &so->remote); } /* dns_so_tcp_keep() */ /* Convenience functions for sending non-DNS data. */ /* Set up everything for sending LENGTH octets. Returns the buffer for the data. */ static unsigned char *dns_so_tcp_send_buffer(struct dns_socket *so, size_t length) { /* Skip the length octets, we are not doing DNS. */ so->qout = 2; so->query->end = length; return so->query->data; } /* Set up everything for receiving LENGTH octets. */ static void dns_so_tcp_recv_expect(struct dns_socket *so, size_t length) { /* Skip the length octets, we are not doing DNS. */ so->apos = 2; so->alen = length; } /* Returns the buffer containing the received data. */ static unsigned char *dns_so_tcp_recv_buffer(struct dns_socket *so) { return so->answer->data; } #if GPGRT_GCC_VERSION >= 80000 # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Warray-bounds" #elif defined __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Warray-bounds" #endif static int dns_so_tcp_send(struct dns_socket *so) { unsigned char *qsrc; size_t qend; int error; size_t n; so->query->data[-2] = 0xff & (so->query->end >> 8); so->query->data[-1] = 0xff & (so->query->end >> 0); qend = so->query->end + 2; while (so->qout < qend) { qsrc = &so->query->data[-2] + so->qout; n = dns_send_nopipe(so->tcp, (void *)qsrc, qend - so->qout, 0, &error); dns_trace_sys_send(so->trace, so->tcp, SOCK_STREAM, qsrc, n, error); if (error) return error; so->qout += n; so->stat.tcp.sent.bytes += n; } so->stat.tcp.sent.count++; return 0; } /* dns_so_tcp_send() */ static int dns_so_tcp_recv(struct dns_socket *so) { unsigned char *asrc; size_t aend, alen, n; int error; aend = so->alen + 2; while (so->apos < aend) { asrc = &so->answer->data[-2]; n = dns_recv(so->tcp, (void *)&asrc[so->apos], aend - so->apos, 0, &error); dns_trace_sys_recv(so->trace, so->tcp, SOCK_STREAM, &asrc[so->apos], n, error); if (error) return error; so->apos += n; so->stat.tcp.rcvd.bytes += n; if (so->alen == 0 && so->apos >= 2) { alen = ((0xff & so->answer->data[-2]) << 8) | ((0xff & so->answer->data[-1]) << 0); if ((error = dns_so_newanswer(so, alen))) return error; so->alen = alen; aend = alen + 2; } } so->answer->end = so->alen; so->stat.tcp.rcvd.count++; return 0; } /* dns_so_tcp_recv() */ #if GPGRT_GCC_VERSION >= 80000 # pragma GCC diagnostic pop #elif __clang__ # pragma clang diagnostic pop #endif int dns_so_check(struct dns_socket *so) { int error; size_t n; unsigned char *buffer; retry: switch (so->state) { case DNS_SO_UDP_INIT: if (so->remote.ss_family != so->local.ss_family) { /* Family mismatch. Reinitialize. */ if ((error = dns_so_closefd(so, &so->udp))) goto error; if ((error = dns_so_closefd(so, &so->tcp))) goto error; /* If the user supplied an interface statement, that is gone now. Sorry. */ memset(&so->local, 0, sizeof so->local); so->local.ss_family = so->remote.ss_family; if (-1 == (so->udp = dns_socket((struct sockaddr *)&so->local, SOCK_DGRAM, &error))) goto error; } so->state++; /* FALL THROUGH */ case DNS_SO_UDP_CONN: udp_connect_retry: error = dns_connect(so->udp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote)); dns_trace_sys_connect(so->trace, so->udp, SOCK_DGRAM, (struct sockaddr *)&so->remote, error); /* Linux returns EINVAL when address was bound to localhost and it's external IP address now. */ if (error == EINVAL) { struct sockaddr unspec_addr; memset (&unspec_addr, 0, sizeof unspec_addr); unspec_addr.sa_family = AF_UNSPEC; connect(so->udp, &unspec_addr, sizeof unspec_addr); goto udp_connect_retry; } else if (error == ECONNREFUSED) /* Error for previous socket operation may be reserved(?) asynchronously. */ goto udp_connect_retry; if (error) goto error; so->state++; /* FALL THROUGH */ case DNS_SO_UDP_SEND: n = dns_send(so->udp, (void *)so->query->data, so->query->end, 0, &error); dns_trace_sys_send(so->trace, so->udp, SOCK_DGRAM, so->query->data, n, error); if (error) goto error; so->stat.udp.sent.bytes += n; so->stat.udp.sent.count++; so->state++; /* FALL THROUGH */ case DNS_SO_UDP_RECV: n = dns_recv(so->udp, (void *)so->answer->data, so->answer->size, 0, &error); dns_trace_sys_recv(so->trace, so->udp, SOCK_DGRAM, so->answer->data, n, error); if (error) goto error; so->answer->end = n; so->stat.udp.rcvd.bytes += n; so->stat.udp.rcvd.count++; if ((error = dns_so_verify(so, so->answer))) goto trash; so->state++; /* FALL THROUGH */ case DNS_SO_UDP_DONE: if (!dns_header(so->answer)->tc || so->type == SOCK_DGRAM) return 0; so->state++; /* FALL THROUGH */ case DNS_SO_TCP_INIT: if (so->remote.ss_family != so->local.ss_family) { /* Family mismatch. Reinitialize. */ if ((error = dns_so_closefd(so, &so->udp))) goto error; if ((error = dns_so_closefd(so, &so->tcp))) goto error; /* If the user supplied an interface statement, that is gone now. Sorry. */ memset(&so->local, 0, sizeof so->local); so->local.ss_family = so->remote.ss_family; } if (dns_so_tcp_keep(so)) { so->state = DNS_SO_TCP_SEND; goto retry; } if ((error = dns_so_closefd(so, &so->tcp))) goto error; if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error))) goto error; so->state++; /* FALL THROUGH */ case DNS_SO_TCP_CONN: error = dns_connect(so->tcp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote)); dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)&so->remote, error); if (error && error != DNS_EISCONN) goto error; so->state++; /* FALL THROUGH */ case DNS_SO_TCP_SEND: if ((error = dns_so_tcp_send(so))) goto error; so->state++; /* FALL THROUGH */ case DNS_SO_TCP_RECV: if ((error = dns_so_tcp_recv(so))) goto error; so->state++; /* FALL THROUGH */ case DNS_SO_TCP_DONE: /* close unless DNS_RESCONF_TCP_ONLY (see dns_res_tcp2type) */ if (so->type != SOCK_STREAM) { if ((error = dns_so_closefd(so, &so->tcp))) goto error; } if ((error = dns_so_verify(so, so->answer))) goto error; return 0; case DNS_SO_SOCKS_INIT: if ((error = dns_so_closefd(so, &so->tcp))) goto error; if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error))) goto error; so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_CONN: { unsigned char method; error = dns_connect(so->tcp, (struct sockaddr *)so->opts.socks_host, dns_sa_len(so->opts.socks_host)); dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)so->opts.socks_host, error); if (error && error != DNS_EISCONN) goto error; /* We need to do a handshake with the SOCKS server, * but the query is already in the buffer. Move it * out of the way. */ dns_p_movptr(&so->query_backup, &so->query); /* Create a new buffer for the handshake. */ dns_p_grow(&so->query); /* Negotiate method. */ buffer = dns_so_tcp_send_buffer(so, 3); buffer[0] = 5; /* RFC-1928 VER field. */ buffer[1] = 1; /* NMETHODS */ if (so->opts.socks_user) method = 2; /* Method: username/password authentication. */ else method = 0; /* Method: No authentication required. */ buffer[2] = method; so->state++; } /* FALL THROUGH */ case DNS_SO_SOCKS_HELLO_SEND: if ((error = dns_so_tcp_send(so))) goto error; dns_so_tcp_recv_expect(so, 2); so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_HELLO_RECV: { unsigned char method; if ((error = dns_so_tcp_recv(so))) goto error; buffer = dns_so_tcp_recv_buffer(so); method = so->opts.socks_user ? 2 : 0; if (buffer[0] != 5 || buffer[1] != method) { /* Socks server returned wrong version or does not support our requested method. */ error = ENOTSUP; /* Fixme: Is there a better errno? */ goto error; } if (method == 0) { /* No authentication, go ahead and send the request. */ so->state = DNS_SO_SOCKS_REQUEST_PREPARE; goto retry; } /* Prepare username/password sub-negotiation. */ if (! so->opts.socks_password) { error = EINVAL; /* No password given. */ goto error; } else { size_t buflen, ulen, plen; ulen = strlen(so->opts.socks_user); plen = strlen(so->opts.socks_password); if (!ulen || ulen > 255 || !plen || plen > 255) { error = EINVAL; /* Credentials too long or too short. */ goto error; } buffer = dns_so_tcp_send_buffer(so, 3 + ulen + plen); buffer[0] = 1; /* VER of the sub-negotiation. */ buffer[1] = (unsigned char) ulen; buflen = 2; memcpy (buffer+buflen, so->opts.socks_user, ulen); buflen += ulen; buffer[buflen++] = (unsigned char) plen; memcpy (buffer+buflen, so->opts.socks_password, plen); } so->state++; } /* FALL THROUGH */ case DNS_SO_SOCKS_AUTH_SEND: if ((error = dns_so_tcp_send(so))) goto error; /* Skip the two length octets, and receive two octets. */ dns_so_tcp_recv_expect(so, 2); so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_AUTH_RECV: if ((error = dns_so_tcp_recv(so))) goto error; buffer = dns_so_tcp_recv_buffer(so); if (buffer[0] != 1) { /* SOCKS server returned wrong version. */ error = EPROTO; goto error; } if (buffer[1]) { /* SOCKS server denied access. */ error = EACCES; goto error; } so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_REQUEST_PREPARE: /* Send request details (rfc-1928, 4). */ buffer = dns_so_tcp_send_buffer(so, so->remote.ss_family == AF_INET6 ? 22 : 10); buffer[0] = 5; /* VER */ buffer[1] = 1; /* CMD = CONNECT */ buffer[2] = 0; /* RSV */ if (so->remote.ss_family == AF_INET6) { struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&so->remote; buffer[3] = 4; /* ATYP = IPv6 */ memcpy (buffer+ 4, &addr_in6->sin6_addr.s6_addr, 16); /* DST.ADDR */ memcpy (buffer+20, &addr_in6->sin6_port, 2); /* DST.PORT */ } else { struct sockaddr_in *addr_in = (struct sockaddr_in *)&so->remote; buffer[3] = 1; /* ATYP = IPv4 */ memcpy (buffer+4, &addr_in->sin_addr.s_addr, 4); /* DST.ADDR */ memcpy (buffer+8, &addr_in->sin_port, 2); /* DST.PORT */ } so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_REQUEST_SEND: if ((error = dns_so_tcp_send(so))) goto error; /* Expect ten octets. This is the length of the * response assuming a IPv4 address is used. */ dns_so_tcp_recv_expect(so, 10); so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_REQUEST_RECV: if ((error = dns_so_tcp_recv(so))) goto error; buffer = dns_so_tcp_recv_buffer(so); if (buffer[0] != 5 || buffer[2] != 0) { /* Socks server returned wrong version or the reserved field is not zero. */ error = EPROTO; goto error; } if (buffer[1]) { switch (buffer[1]) { case 0x01: /* general SOCKS server failure. */ error = ENETDOWN; break; case 0x02: /* connection not allowed by ruleset. */ error = EACCES; break; case 0x03: /* Network unreachable */ error = ENETUNREACH; break; case 0x04: /* Host unreachable */ error = EHOSTUNREACH; break; case 0x05: /* Connection refused */ error = ECONNREFUSED; break; case 0x06: /* TTL expired */ error = ETIMEDOUT; break; case 0x08: /* Address type not supported */ error = EPROTONOSUPPORT; break; case 0x07: /* Command not supported */ default: error = ENOTSUP; /* Fixme: Is there a better error? */ break; } goto error; } if (buffer[3] == 1) { /* This was indeed an IPv4 address. */ so->state = DNS_SO_SOCKS_HANDSHAKE_DONE; goto retry; } if (buffer[3] != 4) { error = ENOTSUP; goto error; } /* Expect receive twelve octets. This accounts for * the remaining bytes assuming an IPv6 address is * used. */ dns_so_tcp_recv_expect(so, 12); so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_REQUEST_RECV_V6: if ((error = dns_so_tcp_recv(so))) goto error; so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_HANDSHAKE_DONE: /* We have not way to store the actual address used by * the server. Then again, we don't really care. */ /* Restore the query. */ dns_p_movptr(&so->query, &so->query_backup); /* Reset cursors. */ so->qout = 0; so->apos = 0; so->alen = 0; /* SOCKS handshake is done. Proceed with the * lookup. */ so->state = DNS_SO_TCP_SEND; goto retry; default: error = DNS_EUNKNOWN; goto error; } /* switch() */ trash: DNS_CARP("discarding packet"); goto retry; error: switch (error) { case DNS_EINTR: goto retry; case DNS_EINPROGRESS: /* FALL THROUGH */ case DNS_EALREADY: /* FALL THROUGH */ #if DNS_EWOULDBLOCK != DNS_EAGAIN case DNS_EWOULDBLOCK: /* FALL THROUGH */ #endif error = DNS_EAGAIN; break; } /* switch() */ return error; } /* dns_so_check() */ struct dns_packet *dns_so_fetch(struct dns_socket *so, int *error) { struct dns_packet *answer; switch (so->state) { case DNS_SO_UDP_DONE: case DNS_SO_TCP_DONE: answer = so->answer; so->answer = 0; dns_trace_so_fetch(so->trace, answer, 0); return answer; default: *error = DNS_EUNKNOWN; dns_trace_so_fetch(so->trace, NULL, *error); return 0; } } /* dns_so_fetch() */ struct dns_packet *dns_so_query(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host, int *error_) { struct dns_packet *A; int error; if (!so->state) { if ((error = dns_so_submit(so, Q, host))) goto error; } if ((error = dns_so_check(so))) goto error; if (!(A = dns_so_fetch(so, &error))) goto error; dns_so_reset(so); return A; error: *error_ = error; return 0; } /* dns_so_query() */ time_t dns_so_elapsed(struct dns_socket *so) { return dns_elapsed(&so->elapsed); } /* dns_so_elapsed() */ void dns_so_clear(struct dns_socket *so) { dns_so_closefds(so, DNS_SO_CLOSE_OLD); } /* dns_so_clear() */ static int dns_so_events2(struct dns_socket *so, enum dns_events type) { int events = 0; switch (so->state) { case DNS_SO_UDP_CONN: case DNS_SO_UDP_SEND: events |= DNS_POLLOUT; break; case DNS_SO_UDP_RECV: events |= DNS_POLLIN; break; case DNS_SO_TCP_CONN: case DNS_SO_TCP_SEND: events |= DNS_POLLOUT; break; case DNS_SO_TCP_RECV: events |= DNS_POLLIN; break; } /* switch() */ switch (type) { case DNS_LIBEVENT: return DNS_POLL2EV(events); default: return events; } /* switch() */ } /* dns_so_events2() */ int dns_so_events(struct dns_socket *so) { return dns_so_events2(so, so->opts.events); } /* dns_so_events() */ int dns_so_pollfd(struct dns_socket *so) { switch (so->state) { case DNS_SO_UDP_CONN: case DNS_SO_UDP_SEND: case DNS_SO_UDP_RECV: return so->udp; case DNS_SO_TCP_CONN: case DNS_SO_TCP_SEND: case DNS_SO_TCP_RECV: return so->tcp; } /* switch() */ return -1; } /* dns_so_pollfd() */ int dns_so_poll(struct dns_socket *so, int timeout) { return dns_poll(dns_so_pollfd(so), dns_so_events2(so, DNS_SYSPOLL), timeout); } /* dns_so_poll() */ const struct dns_stat *dns_so_stat(struct dns_socket *so) { return &so->stat; } /* dns_so_stat() */ struct dns_trace *dns_so_trace(struct dns_socket *so) { return so->trace; } /* dns_so_trace() */ void dns_so_settrace(struct dns_socket *so, struct dns_trace *trace) { struct dns_trace *otrace = so->trace; so->trace = dns_trace_acquire_p(trace); dns_trace_close(otrace); } /* dns_so_settrace() */ /* * R E S O L V E R R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ enum dns_res_state { DNS_R_INIT, DNS_R_GLUE, DNS_R_SWITCH, /* (B)IND, (F)ILE, (C)ACHE */ DNS_R_FILE, /* Lookup in local hosts database */ DNS_R_CACHE, /* Lookup in application cache */ DNS_R_SUBMIT, DNS_R_CHECK, DNS_R_FETCH, DNS_R_BIND, /* Lookup in the network */ DNS_R_SEARCH, DNS_R_HINTS, DNS_R_ITERATE, DNS_R_FOREACH_NS, DNS_R_RESOLV0_NS, /* Prologue: Setup next frame and recurse */ DNS_R_RESOLV1_NS, /* Epilog: Inspect answer */ DNS_R_FOREACH_A, DNS_R_QUERY_A, DNS_R_FOREACH_AAAA, DNS_R_QUERY_AAAA, DNS_R_CNAME0_A, DNS_R_CNAME1_A, DNS_R_FINISH, DNS_R_SMART0_A, DNS_R_SMART1_A, DNS_R_DONE, DNS_R_SERVFAIL, }; /* enum dns_res_state */ #define DNS_R_MAXDEPTH 8 #define DNS_R_ENDFRAME (DNS_R_MAXDEPTH - 1) struct dns_resolver { struct dns_socket so; struct dns_resolv_conf *resconf; struct dns_hosts *hosts; struct dns_hints *hints; struct dns_cache *cache; struct dns_trace *trace; dns_atomic_t refcount; /* Reset zeroes everything below here. */ char qname[DNS_D_MAXNAME + 1]; size_t qlen; enum dns_type qtype; enum dns_class qclass; struct dns_clock elapsed; dns_resconf_i_t search; struct dns_rr_i smart; struct dns_packet *nodata; /* answer if nothing better */ unsigned sp; struct dns_res_frame { enum dns_res_state state; int error; int which; /* (B)IND, (F)ILE; index into resconf->lookup */ int qflags; unsigned attempts; struct dns_packet *query, *answer, *hints; struct dns_rr_i hints_i, hints_j; struct dns_rr hints_ns, ans_cname; } stack[DNS_R_MAXDEPTH]; }; /* struct dns_resolver */ static int dns_res_tcp2type(int tcp) { switch (tcp) { case DNS_RESCONF_TCP_ONLY: case DNS_RESCONF_TCP_SOCKS: return SOCK_STREAM; case DNS_RESCONF_TCP_DISABLE: return SOCK_DGRAM; default: return 0; } } /* dns_res_tcp2type() */ struct dns_resolver *dns_res_open(struct dns_resolv_conf *resconf, struct dns_hosts *hosts, struct dns_hints *hints, struct dns_cache *cache, const struct dns_options *opts, int *_error) { static const struct dns_resolver R_initializer = { .refcount = 1, }; struct dns_resolver *R = 0; int type, error; /* * Grab ref count early because the caller may have passed us a mortal * reference, and we want to do the right thing if we return early * from an error. */ if (resconf) dns_resconf_acquire(resconf); if (hosts) dns_hosts_acquire(hosts); if (hints) dns_hints_acquire(hints); if (cache) dns_cache_acquire(cache); /* * Don't try to load it ourselves because a NULL object might be an * error from, say, dns_resconf_root(), and loading * dns_resconf_local() by default would create undesirable surprises. */ if (!resconf || !hosts || !hints) { if (!*_error) *_error = EINVAL; goto _error; } if (!(R = malloc(sizeof *R))) goto syerr; *R = R_initializer; type = dns_res_tcp2type(resconf->options.tcp); if (!dns_so_init(&R->so, (struct sockaddr *)&resconf->iface, type, opts, &error)) goto error; R->resconf = resconf; R->hosts = hosts; R->hints = hints; R->cache = cache; return R; syerr: error = dns_syerr(); error: *_error = error; _error: dns_res_close(R); dns_resconf_close(resconf); dns_hosts_close(hosts); dns_hints_close(hints); dns_cache_close(cache); return 0; } /* dns_res_open() */ struct dns_resolver *dns_res_stub(const struct dns_options *opts, int *error) { struct dns_resolv_conf *resconf = 0; struct dns_hosts *hosts = 0; struct dns_hints *hints = 0; struct dns_resolver *res = 0; if (!(resconf = dns_resconf_local(error))) goto epilog; if (!(hosts = dns_hosts_local(error))) goto epilog; if (!(hints = dns_hints_local(resconf, error))) goto epilog; /* RESCONF is closed by dns_hints_local, so, get it again. */ if (!(resconf = dns_resconf_local(error))) goto epilog; if (!(res = dns_res_open(resconf, hosts, hints, NULL, opts, error))) goto epilog; else return res; epilog: dns_resconf_close(resconf); dns_hosts_close(hosts); dns_hints_close(hints); return res; } /* dns_res_stub() */ static void dns_res_frame_destroy(struct dns_resolver *R, struct dns_res_frame *frame) { (void)R; dns_p_setptr(&frame->query, NULL); dns_p_setptr(&frame->answer, NULL); dns_p_setptr(&frame->hints, NULL); } /* dns_res_frame_destroy() */ static void dns_res_frame_init(struct dns_resolver *R, struct dns_res_frame *frame) { memset(frame, '\0', sizeof *frame); /* * NB: Can be invoked from dns_res_open, before R->resconf has been * initialized. */ if (R->resconf) { if (!R->resconf->options.recurse) frame->qflags |= DNS_Q_RD; if (R->resconf->options.edns0) frame->qflags |= DNS_Q_EDNS0; } } /* dns_res_frame_init() */ static void dns_res_frame_reset(struct dns_resolver *R, struct dns_res_frame *frame) { dns_res_frame_destroy(R, frame); dns_res_frame_init(R, frame); } /* dns_res_frame_reset() */ static dns_error_t dns_res_frame_prepare(struct dns_resolver *R, struct dns_res_frame *F, const char *qname, enum dns_type qtype, enum dns_class qclass) { struct dns_packet *P = NULL; if (!(F < endof(R->stack))) return DNS_EUNKNOWN; dns_p_movptr(&P, &F->query); dns_res_frame_reset(R, F); dns_p_movptr(&F->query, &P); return dns_q_make(&F->query, qname, qtype, qclass, F->qflags); } /* dns_res_frame_prepare() */ void dns_res_reset(struct dns_resolver *R) { unsigned i; dns_so_reset(&R->so); dns_p_setptr(&R->nodata, NULL); for (i = 0; i < lengthof(R->stack); i++) dns_res_frame_destroy(R, &R->stack[i]); memset(&R->qname, '\0', sizeof *R - offsetof(struct dns_resolver, qname)); for (i = 0; i < lengthof(R->stack); i++) dns_res_frame_init(R, &R->stack[i]); } /* dns_res_reset() */ void dns_res_close(struct dns_resolver *R) { if (!R || 1 < dns_res_release(R)) return; dns_res_reset(R); dns_so_destroy(&R->so); dns_hints_close(R->hints); dns_hosts_close(R->hosts); dns_resconf_close(R->resconf); dns_cache_close(R->cache); dns_trace_close(R->trace); free(R); } /* dns_res_close() */ dns_refcount_t dns_res_acquire(struct dns_resolver *R) { return dns_atomic_fetch_add(&R->refcount); } /* dns_res_acquire() */ dns_refcount_t dns_res_release(struct dns_resolver *R) { return dns_atomic_fetch_sub(&R->refcount); } /* dns_res_release() */ struct dns_resolver *dns_res_mortal(struct dns_resolver *res) { if (res) dns_res_release(res); return res; } /* dns_res_mortal() */ static struct dns_packet *dns_res_merge(struct dns_packet *P0, struct dns_packet *P1, int *error_) { size_t bufsiz = P0->end + P1->end; struct dns_packet *P[3] = { P0, P1, 0 }; struct dns_rr rr[3]; int error, copy, i; enum dns_section section; retry: if (!(P[2] = dns_p_make(bufsiz, &error))) goto error; dns_rr_foreach(&rr[0], P[0], .section = DNS_S_QD) { if ((error = dns_rr_copy(P[2], &rr[0], P[0]))) goto error; } for (section = DNS_S_AN; (DNS_S_ALL & section); section <<= 1) { for (i = 0; i < 2; i++) { dns_rr_foreach(&rr[i], P[i], .section = section) { copy = 1; dns_rr_foreach(&rr[2], P[2], .type = rr[i].type, .section = (DNS_S_ALL & ~DNS_S_QD)) { if (0 == dns_rr_cmp(&rr[i], P[i], &rr[2], P[2])) { copy = 0; break; } } if (copy && (error = dns_rr_copy(P[2], &rr[i], P[i]))) { if (error == DNS_ENOBUFS && bufsiz < 65535) { dns_p_setptr(&P[2], NULL); bufsiz = DNS_PP_MAX(65535, bufsiz * 2); goto retry; } goto error; } } /* foreach(rr) */ } /* foreach(packet) */ } /* foreach(section) */ return P[2]; error: *error_ = error; dns_p_free(P[2]); return 0; } /* dns_res_merge() */ static struct dns_packet *dns_res_glue(struct dns_resolver *R, struct dns_packet *Q) { union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 }; struct dns_packet *P = dns_p_init(&P_instance.p, 512); char qname[DNS_D_MAXNAME + 1]; size_t qlen; enum dns_type qtype; struct dns_rr rr; unsigned sp; int error; if (!(qlen = dns_d_expand(qname, sizeof qname, 12, Q, &error)) || qlen >= sizeof qname) return 0; if (!(qtype = dns_rr_type(12, Q))) return 0; if ((error = dns_p_push(P, DNS_S_QD, qname, strlen(qname), qtype, DNS_C_IN, 0, 0))) return 0; for (sp = 0; sp <= R->sp; sp++) { if (!R->stack[sp].answer) continue; dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = qtype, .section = (DNS_S_ALL & ~DNS_S_QD)) { rr.section = DNS_S_AN; if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer))) return 0; } } if (dns_p_count(P, DNS_S_AN) > 0) goto copy; /* Otherwise, look for a CNAME */ for (sp = 0; sp <= R->sp; sp++) { if (!R->stack[sp].answer) continue; dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = DNS_T_CNAME, .section = (DNS_S_ALL & ~DNS_S_QD)) { rr.section = DNS_S_AN; if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer))) return 0; } } if (!dns_p_count(P, DNS_S_AN)) return 0; copy: return dns_p_copy(dns_p_make(P->end, &error), P); } /* dns_res_glue() */ /* * Sort NS records by three criteria: * * 1) Whether glue is present. * 2) Whether glue record is original or of recursive lookup. * 3) Randomly shuffle records which share the above criteria. * * NOTE: Assumes only NS records passed, AND ASSUMES no new NS records will * be added during an iteration. * * FIXME: Only groks A glue, not AAAA glue. */ static int dns_res_nameserv_cmp(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { _Bool glued[2] = { 0 }; struct dns_rr x = { 0 }, y = { 0 }; struct dns_ns ns; int cmp, error; if (!(error = dns_ns_parse(&ns, a, P))) { struct dns_rr_i I_instance = { 0 }; I_instance.section = (DNS_S_ALL & ~DNS_S_QD); I_instance.name = ns.host; I_instance.type = DNS_T_A; glued[0] = !!dns_rr_grep(&x, 1, &I_instance, P, &error); } if (!(error = dns_ns_parse(&ns, b, P))) { struct dns_rr_i I_instance = { 0 }; I_instance.section = (DNS_S_ALL & ~DNS_S_QD); I_instance.name = ns.host; I_instance.type = DNS_T_A; glued[1] = !!dns_rr_grep(&y, 1, &I_instance, P, &error); } if ((cmp = glued[1] - glued[0])) { return cmp; } else if ((cmp = (dns_rr_offset(&y) < i->args[0]) - (dns_rr_offset(&x) < i->args[0]))) { return cmp; } else { return dns_rr_i_shuffle(a, b, i, P); } } /* dns_res_nameserv_cmp() */ #define dgoto(sp, i) \ do { R->stack[(sp)].state = (i); goto exec; } while (0) static int dns_res_exec(struct dns_resolver *R) { struct dns_res_frame *F; struct dns_packet *P; union { char host[DNS_D_MAXNAME + 1]; char name[DNS_D_MAXNAME + 1]; struct dns_ns ns; struct dns_cname cname; } u; size_t len; struct dns_rr rr; int error; exec: F = &R->stack[R->sp]; switch (F->state) { case DNS_R_INIT: F->state++; /* FALL THROUGH */ case DNS_R_GLUE: if (R->sp == 0) dgoto(R->sp, DNS_R_SWITCH); if (!F->query) goto noquery; if (!(F->answer = dns_res_glue(R, F->query))) dgoto(R->sp, DNS_R_SWITCH); if (!(len = dns_d_expand(u.name, sizeof u.name, 12, F->query, &error))) goto error; else if (len >= sizeof u.name) goto toolong; dns_rr_foreach(&rr, F->answer, .name = u.name, .type = dns_rr_type(12, F->query), .section = DNS_S_AN) { dgoto(R->sp, DNS_R_FINISH); } dns_rr_foreach(&rr, F->answer, .name = u.name, .type = DNS_T_CNAME, .section = DNS_S_AN) { F->ans_cname = rr; dgoto(R->sp, DNS_R_CNAME0_A); } F->state++; case DNS_R_SWITCH: while (F->which < (int)sizeof R->resconf->lookup && R->resconf->lookup[F->which]) { switch (R->resconf->lookup[F->which++]) { case 'b': case 'B': dgoto(R->sp, DNS_R_BIND); case 'f': case 'F': dgoto(R->sp, DNS_R_FILE); case 'c': case 'C': if (R->cache) dgoto(R->sp, DNS_R_CACHE); break; default: break; } } /* * FIXME: Examine more closely whether our logic is correct * and DNS_R_SERVFAIL is the correct default response. * * Case 1: We got here because we never got an answer on the * wire. All queries timed-out and we reached maximum * attempts count. See DNS_R_FOREACH_NS. In that case * DNS_R_SERVFAIL is the correct state, unless we want to * return DNS_ETIMEDOUT. * * Case 2: We were a stub resolver and got an unsatisfactory * answer (empty ANSWER section) which caused us to jump * back to DNS_R_SEARCH and ultimately to DNS_R_SWITCH. We * return the answer returned from the wire, which we * stashed in R->nodata. * * Case 3: We reached maximum attempts count as in case #1, * but never got an authoritative response which caused us * to short-circuit. See end of DNS_R_QUERY_A case. We * should probably prepare R->nodata as in case #2. */ if (R->sp == 0 && R->nodata) { /* XXX: can we just return nodata regardless? */ dns_p_movptr(&F->answer, &R->nodata); dgoto(R->sp, DNS_R_FINISH); } dgoto(R->sp, DNS_R_SERVFAIL); case DNS_R_FILE: if (R->sp > 0) { if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error))) goto error; if (dns_p_count(F->answer, DNS_S_AN) > 0) dgoto(R->sp, DNS_R_FINISH); dns_p_setptr(&F->answer, NULL); } else { R->search = 0; while ((len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search))) { if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags))) goto error; if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error))) goto error; if (dns_p_count(F->answer, DNS_S_AN) > 0) dgoto(R->sp, DNS_R_FINISH); dns_p_setptr(&F->answer, NULL); } } dgoto(R->sp, DNS_R_SWITCH); case DNS_R_CACHE: error = 0; if (!F->query && (error = dns_q_make(&F->query, R->qname, R->qtype, R->qclass, F->qflags))) goto error; if (dns_p_setptr(&F->answer, R->cache->query(F->query, R->cache, &error))) { if (dns_p_count(F->answer, DNS_S_AN) > 0) dgoto(R->sp, DNS_R_FINISH); dns_p_setptr(&F->answer, NULL); dgoto(R->sp, DNS_R_SWITCH); } else if (error) goto error; F->state++; /* FALL THROUGH */ case DNS_R_SUBMIT: if ((error = R->cache->submit(F->query, R->cache))) goto error; F->state++; /* FALL THROUGH */ case DNS_R_CHECK: if ((error = R->cache->check(R->cache))) goto error; F->state++; /* FALL THROUGH */ case DNS_R_FETCH: error = 0; if (dns_p_setptr(&F->answer, R->cache->fetch(R->cache, &error))) { if (dns_p_count(F->answer, DNS_S_AN) > 0) dgoto(R->sp, DNS_R_FINISH); dns_p_setptr(&F->answer, NULL); dgoto(R->sp, DNS_R_SWITCH); } else if (error) goto error; dgoto(R->sp, DNS_R_SWITCH); case DNS_R_BIND: if (R->sp > 0) { if (!F->query) goto noquery; dgoto(R->sp, DNS_R_HINTS); } R->search = 0; F->state++; /* FALL THROUGH */ case DNS_R_SEARCH: /* * XXX: We probably should only apply the domain search * algorithm if R->sp == 0. */ if (!(len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search))) dgoto(R->sp, DNS_R_SWITCH); if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags))) goto error; F->state++; /* FALL THROUGH */ case DNS_R_HINTS: if (!dns_p_setptr(&F->hints, dns_hints_query(R->hints, F->query, &error))) goto error; F->state++; /* FALL THROUGH */ case DNS_R_ITERATE: dns_rr_i_init(&F->hints_i); F->hints_i.section = DNS_S_AUTHORITY; F->hints_i.type = DNS_T_NS; F->hints_i.sort = &dns_res_nameserv_cmp; F->hints_i.args[0] = F->hints->end; F->state++; /* FALL THROUGH */ case DNS_R_FOREACH_NS: dns_rr_i_save(&F->hints_i); /* Load our next nameserver host. */ if (!dns_rr_grep(&F->hints_ns, 1, &F->hints_i, F->hints, &error)) { if (++F->attempts < R->resconf->options.attempts) dgoto(R->sp, DNS_R_ITERATE); dgoto(R->sp, DNS_R_SWITCH); } dns_rr_i_init(&F->hints_j); /* Assume there are glue records */ dgoto(R->sp, DNS_R_FOREACH_A); case DNS_R_RESOLV0_NS: /* Have we reached our max depth? */ if (&F[1] >= endof(R->stack)) dgoto(R->sp, DNS_R_FOREACH_NS); if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints))) goto error; if ((error = dns_res_frame_prepare(R, &F[1], u.ns.host, DNS_T_A, DNS_C_IN))) goto error; F->state++; dgoto(++R->sp, DNS_R_INIT); case DNS_R_RESOLV1_NS: if (!(len = dns_d_expand(u.host, sizeof u.host, 12, F[1].query, &error))) goto error; else if (len >= sizeof u.host) goto toolong; dns_rr_foreach(&rr, F[1].answer, .name = u.host, .type = DNS_T_A, .section = (DNS_S_ALL & ~DNS_S_QD)) { rr.section = DNS_S_AR; if ((error = dns_rr_copy(F->hints, &rr, F[1].answer))) goto error; dns_rr_i_rewind(&F->hints_i); /* Now there's glue. */ } dgoto(R->sp, DNS_R_FOREACH_NS); case DNS_R_FOREACH_A: { struct dns_a a; struct sockaddr_in sin; /* * NOTE: Iterator initialized in DNS_R_FOREACH_NS because * this state is re-entrant, but we need to reset * .name to a valid pointer each time. */ if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints))) goto error; F->hints_j.name = u.ns.host; F->hints_j.type = DNS_T_A; F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; if (!dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { if (!dns_rr_i_count(&F->hints_j)) { /* Check if we have in fact servers with an IPv6 address. */ dns_rr_i_init(&F->hints_j); F->hints_j.name = u.ns.host; F->hints_j.type = DNS_T_AAAA; F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { /* We do. Reinitialize iterator and handle it. */ dns_rr_i_init(&F->hints_j); dgoto(R->sp, DNS_R_FOREACH_AAAA); } dgoto(R->sp, DNS_R_RESOLV0_NS); } dgoto(R->sp, DNS_R_FOREACH_NS); } if ((error = dns_a_parse(&a, &rr, F->hints))) goto error; memset(&sin, '\0', sizeof sin); /* NB: silence valgrind */ sin.sin_family = AF_INET; sin.sin_addr = a.addr; if (R->sp == 0) sin.sin_port = dns_hints_port(R->hints, AF_INET, &sin.sin_addr); else sin.sin_port = htons(53); if (DNS_DEBUG) { char addr[INET_ADDRSTRLEN + 1]; dns_a_print(addr, sizeof addr, &a); dns_header(F->query)->qid = dns_so_mkqid(&R->so); DNS_SHOW(F->query, "ASKING: %s/%s @ DEPTH: %u)", u.ns.host, addr, R->sp); } dns_trace_setcname(R->trace, u.ns.host, (struct sockaddr *)&sin); if ((error = dns_so_submit(&R->so, F->query, (struct sockaddr *)&sin))) goto error; F->state++; } /* FALL THROUGH */ case DNS_R_QUERY_A: if (dns_so_elapsed(&R->so) >= dns_resconf_timeout(R->resconf)) dgoto(R->sp, DNS_R_FOREACH_A); error = dns_so_check(&R->so); if (R->so.state != DNS_SO_SOCKS_CONN && error == ECONNREFUSED) dgoto(R->sp, DNS_R_FOREACH_A); else if (error) goto error; if (!dns_p_setptr(&F->answer, dns_so_fetch(&R->so, &error))) goto error; if (DNS_DEBUG) { DNS_SHOW(F->answer, "ANSWER @ DEPTH: %u)", R->sp); } if (dns_p_rcode(F->answer) == DNS_RC_FORMERR || dns_p_rcode(F->answer) == DNS_RC_NOTIMP || dns_p_rcode(F->answer) == DNS_RC_BADVERS) { /* Temporarily disable EDNS0 and try again. */ if (F->qflags & DNS_Q_EDNS0) { F->qflags &= ~DNS_Q_EDNS0; if ((error = dns_q_remake(&F->query, F->qflags))) goto error; dgoto(R->sp, DNS_R_FOREACH_A); } } if ((error = dns_rr_parse(&rr, 12, F->query))) goto error; if (!(len = dns_d_expand(u.name, sizeof u.name, rr.dn.p, F->query, &error))) goto error; else if (len >= sizeof u.name) goto toolong; dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = rr.type) { dgoto(R->sp, DNS_R_FINISH); /* Found */ } dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = DNS_T_CNAME) { F->ans_cname = rr; dgoto(R->sp, DNS_R_CNAME0_A); } /* * XXX: The condition here should probably check whether * R->sp == 0, because DNS_R_SEARCH runs regardless of * options.recurse. See DNS_R_BIND. */ if (!R->resconf->options.recurse) { /* Make first answer our tentative answer */ if (!R->nodata) dns_p_movptr(&R->nodata, &F->answer); dgoto(R->sp, DNS_R_SEARCH); } dns_rr_foreach(&rr, F->answer, .section = DNS_S_NS, .type = DNS_T_NS) { dns_p_movptr(&F->hints, &F->answer); dgoto(R->sp, DNS_R_ITERATE); } /* XXX: Should this go further up? */ if (dns_header(F->answer)->aa) dgoto(R->sp, DNS_R_FINISH); /* XXX: Should we copy F->answer to R->nodata? */ dgoto(R->sp, DNS_R_FOREACH_A); case DNS_R_FOREACH_AAAA: { struct dns_aaaa aaaa; struct sockaddr_in6 sin6; /* * NOTE: Iterator initialized in DNS_R_FOREACH_NS because * this state is re-entrant, but we need to reset * .name to a valid pointer each time. */ if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints))) goto error; F->hints_j.name = u.ns.host; F->hints_j.type = DNS_T_AAAA; F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; if (!dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { if (!dns_rr_i_count(&F->hints_j)) { /* Check if we have in fact servers with an IPv4 address. */ dns_rr_i_init(&F->hints_j); F->hints_j.name = u.ns.host; F->hints_j.type = DNS_T_A; F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { /* We do. Reinitialize iterator and handle it. */ dns_rr_i_init(&F->hints_j); dgoto(R->sp, DNS_R_FOREACH_A); } dgoto(R->sp, DNS_R_RESOLV0_NS); } dgoto(R->sp, DNS_R_FOREACH_NS); } if ((error = dns_aaaa_parse(&aaaa, &rr, F->hints))) goto error; memset(&sin6, '\0', sizeof sin6); /* NB: silence valgrind */ sin6.sin6_family = AF_INET6; sin6.sin6_addr = aaaa.addr; if (R->sp == 0) sin6.sin6_port = dns_hints_port(R->hints, AF_INET, &sin6.sin6_addr); else sin6.sin6_port = htons(53); if (DNS_DEBUG) { char addr[INET6_ADDRSTRLEN + 1]; dns_aaaa_print(addr, sizeof addr, &aaaa); dns_header(F->query)->qid = dns_so_mkqid(&R->so); DNS_SHOW(F->query, "ASKING: %s/%s @ DEPTH: %u)", u.ns.host, addr, R->sp); } dns_trace_setcname(R->trace, u.ns.host, (struct sockaddr *)&sin6); if ((error = dns_so_submit(&R->so, F->query, (struct sockaddr *)&sin6))) goto error; F->state++; } /* FALL THROUGH */ case DNS_R_QUERY_AAAA: if (dns_so_elapsed(&R->so) >= dns_resconf_timeout(R->resconf)) dgoto(R->sp, DNS_R_FOREACH_AAAA); error = dns_so_check(&R->so); if (error == ECONNREFUSED) dgoto(R->sp, DNS_R_FOREACH_AAAA); else if (error) goto error; if (!dns_p_setptr(&F->answer, dns_so_fetch(&R->so, &error))) goto error; if (DNS_DEBUG) { DNS_SHOW(F->answer, "ANSWER @ DEPTH: %u)", R->sp); } if (dns_p_rcode(F->answer) == DNS_RC_FORMERR || dns_p_rcode(F->answer) == DNS_RC_NOTIMP || dns_p_rcode(F->answer) == DNS_RC_BADVERS) { /* Temporarily disable EDNS0 and try again. */ if (F->qflags & DNS_Q_EDNS0) { F->qflags &= ~DNS_Q_EDNS0; if ((error = dns_q_remake(&F->query, F->qflags))) goto error; dgoto(R->sp, DNS_R_FOREACH_AAAA); } } if ((error = dns_rr_parse(&rr, 12, F->query))) goto error; if (!(len = dns_d_expand(u.name, sizeof u.name, rr.dn.p, F->query, &error))) goto error; else if (len >= sizeof u.name) goto toolong; dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = rr.type) { dgoto(R->sp, DNS_R_FINISH); /* Found */ } dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = DNS_T_CNAME) { F->ans_cname = rr; dgoto(R->sp, DNS_R_CNAME0_A); } /* * XXX: The condition here should probably check whether * R->sp == 0, because DNS_R_SEARCH runs regardless of * options.recurse. See DNS_R_BIND. */ if (!R->resconf->options.recurse) { /* Make first answer our tentative answer */ if (!R->nodata) dns_p_movptr(&R->nodata, &F->answer); dgoto(R->sp, DNS_R_SEARCH); } dns_rr_foreach(&rr, F->answer, .section = DNS_S_NS, .type = DNS_T_NS) { dns_p_movptr(&F->hints, &F->answer); dgoto(R->sp, DNS_R_ITERATE); } /* XXX: Should this go further up? */ if (dns_header(F->answer)->aa) dgoto(R->sp, DNS_R_FINISH); /* XXX: Should we copy F->answer to R->nodata? */ dgoto(R->sp, DNS_R_FOREACH_AAAA); case DNS_R_CNAME0_A: if (&F[1] >= endof(R->stack)) dgoto(R->sp, DNS_R_FINISH); if ((error = dns_cname_parse(&u.cname, &F->ans_cname, F->answer))) goto error; if ((error = dns_res_frame_prepare(R, &F[1], u.cname.host, dns_rr_type(12, F->query), DNS_C_IN))) goto error; F->state++; dgoto(++R->sp, DNS_R_INIT); case DNS_R_CNAME1_A: if (!(P = dns_res_merge(F->answer, F[1].answer, &error))) goto error; dns_p_setptr(&F->answer, P); dgoto(R->sp, DNS_R_FINISH); case DNS_R_FINISH: if (!F->answer) goto noanswer; if (!R->resconf->options.smart || R->sp > 0) dgoto(R->sp, DNS_R_DONE); R->smart.section = DNS_S_AN; R->smart.type = R->qtype; dns_rr_i_init(&R->smart); F->state++; /* FALL THROUGH */ case DNS_R_SMART0_A: if (&F[1] >= endof(R->stack)) dgoto(R->sp, DNS_R_DONE); while (dns_rr_grep(&rr, 1, &R->smart, F->answer, &error)) { union { struct dns_ns ns; struct dns_mx mx; struct dns_srv srv; } rd; const char *qname; enum dns_type qtype; enum dns_class qclass; switch (rr.type) { case DNS_T_NS: if ((error = dns_ns_parse(&rd.ns, &rr, F->answer))) goto error; qname = rd.ns.host; qtype = DNS_T_A; qclass = DNS_C_IN; break; case DNS_T_MX: if ((error = dns_mx_parse(&rd.mx, &rr, F->answer))) goto error; qname = rd.mx.host; qtype = DNS_T_A; qclass = DNS_C_IN; break; case DNS_T_SRV: if ((error = dns_srv_parse(&rd.srv, &rr, F->answer))) goto error; qname = rd.srv.target; qtype = DNS_T_A; qclass = DNS_C_IN; break; default: continue; } /* switch() */ if ((error = dns_res_frame_prepare(R, &F[1], qname, qtype, qclass))) goto error; F->state++; dgoto(++R->sp, DNS_R_INIT); } /* while() */ /* * NOTE: SMTP specification says to fallback to A record. * * XXX: Should we add a mock MX answer? */ if (R->qtype == DNS_T_MX && R->smart.state.count == 0) { if ((error = dns_res_frame_prepare(R, &F[1], R->qname, DNS_T_A, DNS_C_IN))) goto error; R->smart.state.count++; F->state++; dgoto(++R->sp, DNS_R_INIT); } dgoto(R->sp, DNS_R_DONE); case DNS_R_SMART1_A: if (!F[1].answer) goto noanswer; /* * FIXME: For CNAME chains (which are typically illegal in * this context), we should rewrite the record host name * to the original smart qname. All the user cares about * is locating that A/AAAA record. */ dns_rr_foreach(&rr, F[1].answer, .section = DNS_S_AN, .type = DNS_T_A) { rr.section = DNS_S_AR; if (dns_rr_exists(&rr, F[1].answer, F->answer)) continue; while ((error = dns_rr_copy(F->answer, &rr, F[1].answer))) { if (error != DNS_ENOBUFS) goto error; if ((error = dns_p_grow(&F->answer))) goto error; } } dgoto(R->sp, DNS_R_SMART0_A); case DNS_R_DONE: if (!F->answer) goto noanswer; if (R->sp > 0) dgoto(--R->sp, F[-1].state); break; case DNS_R_SERVFAIL: if (!dns_p_setptr(&F->answer, dns_p_make(DNS_P_QBUFSIZ, &error))) goto error; dns_header(F->answer)->qr = 1; dns_header(F->answer)->rcode = DNS_RC_SERVFAIL; if ((error = dns_p_push(F->answer, DNS_S_QD, R->qname, strlen(R->qname), R->qtype, R->qclass, 0, 0))) goto error; dgoto(R->sp, DNS_R_DONE); default: error = EINVAL; goto error; } /* switch () */ return 0; noquery: error = DNS_ENOQUERY; goto error; noanswer: error = DNS_ENOANSWER; goto error; toolong: error = DNS_EILLEGAL; /* FALL THROUGH */ error: return error; } /* dns_res_exec() */ #undef goto void dns_res_clear(struct dns_resolver *R) { switch (R->stack[R->sp].state) { case DNS_R_CHECK: R->cache->clear(R->cache); break; default: dns_so_clear(&R->so); break; } } /* dns_res_clear() */ static int dns_res_events2(struct dns_resolver *R, enum dns_events type) { int events; switch (R->stack[R->sp].state) { case DNS_R_CHECK: events = R->cache->events(R->cache); return (type == DNS_LIBEVENT)? DNS_POLL2EV(events) : events; default: return dns_so_events2(&R->so, type); } } /* dns_res_events2() */ int dns_res_events(struct dns_resolver *R) { return dns_res_events2(R, R->so.opts.events); } /* dns_res_events() */ int dns_res_pollfd(struct dns_resolver *R) { switch (R->stack[R->sp].state) { case DNS_R_CHECK: return R->cache->pollfd(R->cache); default: return dns_so_pollfd(&R->so); } } /* dns_res_pollfd() */ time_t dns_res_timeout(struct dns_resolver *R) { time_t elapsed; switch (R->stack[R->sp].state) { #if 0 case DNS_R_QUERY_AAAA: #endif case DNS_R_QUERY_A: elapsed = dns_so_elapsed(&R->so); if (elapsed <= dns_resconf_timeout(R->resconf)) return R->resconf->options.timeout - elapsed; break; default: break; } /* switch() */ /* * NOTE: We're not in a pollable state, or the user code hasn't * called dns_res_check properly. The calling code is probably * broken. Put them into a slow-burn pattern. */ return 1; } /* dns_res_timeout() */ time_t dns_res_elapsed(struct dns_resolver *R) { return dns_elapsed(&R->elapsed); } /* dns_res_elapsed() */ int dns_res_poll(struct dns_resolver *R, int timeout) { return dns_poll(dns_res_pollfd(R), dns_res_events2(R, DNS_SYSPOLL), timeout); } /* dns_res_poll() */ int dns_res_submit2(struct dns_resolver *R, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass) { dns_res_reset(R); /* Don't anchor; that can conflict with searchlist generation. */ dns_d_init(R->qname, sizeof R->qname, qname, (R->qlen = qlen), 0); R->qtype = qtype; R->qclass = qclass; dns_begin(&R->elapsed); dns_trace_res_submit(R->trace, R->qname, R->qtype, R->qclass, 0); return 0; } /* dns_res_submit2() */ int dns_res_submit(struct dns_resolver *R, const char *qname, enum dns_type qtype, enum dns_class qclass) { return dns_res_submit2(R, qname, strlen(qname), qtype, qclass); } /* dns_res_submit() */ int dns_res_check(struct dns_resolver *R) { int error; if (R->stack[0].state != DNS_R_DONE) { if ((error = dns_res_exec(R))) return error; } return 0; } /* dns_res_check() */ struct dns_packet *dns_res_fetch(struct dns_resolver *R, int *_error) { struct dns_packet *P = NULL; int error; if (R->stack[0].state != DNS_R_DONE) { error = DNS_EUNKNOWN; goto error; } if (!dns_p_movptr(&P, &R->stack[0].answer)) { error = DNS_EFETCHED; goto error; } dns_trace_res_fetch(R->trace, P, 0); return P; error: *_error = error; dns_trace_res_fetch(R->trace, NULL, error); return NULL; } /* dns_res_fetch() */ static struct dns_packet *dns_res_fetch_and_study(struct dns_resolver *R, int *_error) { struct dns_packet *P = NULL; int error; if (!(P = dns_res_fetch(R, &error))) goto error; if ((error = dns_p_study(P))) goto error; return P; error: *_error = error; dns_p_free(P); return NULL; } /* dns_res_fetch_and_study() */ struct dns_packet *dns_res_query(struct dns_resolver *res, const char *qname, enum dns_type qtype, enum dns_class qclass, int timeout, int *error_) { int error; if ((error = dns_res_submit(res, qname, qtype, qclass))) goto error; while ((error = dns_res_check(res))) { if (dns_res_elapsed(res) > timeout) error = DNS_ETIMEDOUT; if (error != DNS_EAGAIN) goto error; if ((error = dns_res_poll(res, 1))) goto error; } return dns_res_fetch(res, error_); error: *error_ = error; return 0; } /* dns_res_query() */ const struct dns_stat *dns_res_stat(struct dns_resolver *res) { return dns_so_stat(&res->so); } /* dns_res_stat() */ void dns_res_sethints(struct dns_resolver *res, struct dns_hints *hints) { dns_hints_acquire(hints); /* acquire first in case same hints object */ dns_hints_close(res->hints); res->hints = hints; } /* dns_res_sethints() */ struct dns_trace *dns_res_trace(struct dns_resolver *res) { return res->trace; } /* dns_res_trace() */ void dns_res_settrace(struct dns_resolver *res, struct dns_trace *trace) { struct dns_trace *otrace = res->trace; res->trace = dns_trace_acquire_p(trace); dns_trace_close(otrace); dns_so_settrace(&res->so, trace); } /* dns_res_settrace() */ /* * A D D R I N F O R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ struct dns_addrinfo { struct addrinfo hints; struct dns_resolver *res; struct dns_trace *trace; char qname[DNS_D_MAXNAME + 1]; enum dns_type qtype; unsigned short qport, port; struct { unsigned long todo; int state; int atype; enum dns_type qtype; } af; struct dns_packet *answer; struct dns_packet *glue; struct dns_rr_i i, g; struct dns_rr rr; char cname[DNS_D_MAXNAME + 1]; char i_cname[DNS_D_MAXNAME + 1], g_cname[DNS_D_MAXNAME + 1]; int g_depth; int state; int found; struct dns_stat st; }; /* struct dns_addrinfo */ #define DNS_AI_AFMAX 32 #define DNS_AI_AF2INDEX(af) (1UL << ((af) - 1)) static inline unsigned long dns_ai_af2index(int af) { dns_static_assert(dns_same_type(unsigned long, DNS_AI_AF2INDEX(1), 1), "internal type mismatch"); dns_static_assert(dns_same_type(unsigned long, ((struct dns_addrinfo *)0)->af.todo, 1), "internal type mismatch"); return (af > 0 && af <= DNS_AI_AFMAX)? DNS_AI_AF2INDEX(af) : 0; } static int dns_ai_setaf(struct dns_addrinfo *ai, int af, int qtype) { ai->af.atype = af; ai->af.qtype = qtype; ai->af.todo &= ~dns_ai_af2index(af); return af; } /* dns_ai_setaf() */ #define DNS_SM_RESTORE \ do { pc = 0xff & (ai->af.state >> 0); i = 0xff & (ai->af.state >> 8); } while (0) #define DNS_SM_SAVE \ do { ai->af.state = ((0xff & pc) << 0) | ((0xff & i) << 8); } while (0) static int dns_ai_nextaf(struct dns_addrinfo *ai) { int i, pc; dns_static_assert(AF_UNSPEC == 0, "AF_UNSPEC constant not 0"); dns_static_assert(AF_INET <= DNS_AI_AFMAX, "AF_INET constant too large"); dns_static_assert(AF_INET6 <= DNS_AI_AFMAX, "AF_INET6 constant too large"); DNS_SM_ENTER; if (ai->res) { /* * NB: On OpenBSD, at least, the types of entries resolved * is the intersection of the /etc/resolv.conf families and * the families permitted by the .ai_type hint. So if * /etc/resolv.conf has "family inet4" and .ai_type * is AF_INET6, then the address ::1 will return 0 entries * even if AI_NUMERICHOST is specified in .ai_flags. */ while (i < (int)lengthof(ai->res->resconf->family)) { int af = ai->res->resconf->family[i++]; if (af == AF_UNSPEC) { DNS_SM_EXIT; } else if (af < 0 || af > DNS_AI_AFMAX) { continue; } else if (!(DNS_AI_AF2INDEX(af) & ai->af.todo)) { continue; } else if (af == AF_INET) { DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A)); } else if (af == AF_INET6) { DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA)); } } } else { /* * NB: If we get here than AI_NUMERICFLAGS should be set and * order shouldn't matter. */ if (DNS_AI_AF2INDEX(AF_INET) & ai->af.todo) DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A)); if (DNS_AI_AF2INDEX(AF_INET6) & ai->af.todo) DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA)); } DNS_SM_LEAVE; return dns_ai_setaf(ai, AF_UNSPEC, 0); } /* dns_ai_nextaf() */ #undef DNS_SM_RESTORE #undef DNS_SM_SAVE static enum dns_type dns_ai_qtype(struct dns_addrinfo *ai) { return (ai->qtype)? ai->qtype : ai->af.qtype; } /* dns_ai_qtype() */ /* JW: This is not defined on mingw. */ #ifndef AI_NUMERICSERV #define AI_NUMERICSERV 0 #endif static dns_error_t dns_ai_parseport(unsigned short *port, const char *serv, const struct addrinfo *hints) { const char *cp = serv; unsigned long n = 0; while (*cp >= '0' && *cp <= '9' && n < 65536) { n *= 10; n += *cp++ - '0'; } if (*cp == '\0') { if (cp == serv || n >= 65536) return DNS_ESERVICE; *port = n; return 0; } if (hints->ai_flags & AI_NUMERICSERV) return DNS_ESERVICE; /* TODO: try getaddrinfo(NULL, serv, { .ai_flags = AI_NUMERICSERV }) */ return DNS_ESERVICE; } /* dns_ai_parseport() */ struct dns_addrinfo *dns_ai_open(const char *host, const char *serv, enum dns_type qtype, const struct addrinfo *hints, struct dns_resolver *res, int *_error) { static const struct dns_addrinfo ai_initializer; struct dns_addrinfo *ai; int error; if (res) { dns_res_acquire(res); } else if (!(hints->ai_flags & AI_NUMERICHOST)) { /* * NOTE: it's assumed that *_error is set from a previous * API function call, such as dns_res_stub(). Should change * this semantic, but it's applied elsewhere, too. */ if (!*_error) *_error = EINVAL; return NULL; } if (!(ai = malloc(sizeof *ai))) goto syerr; *ai = ai_initializer; ai->hints = *hints; ai->res = res; res = NULL; if (sizeof ai->qname <= dns_strlcpy(ai->qname, host, sizeof ai->qname)) { error = ENAMETOOLONG; goto error; } ai->qtype = qtype; ai->qport = 0; if (serv && (error = dns_ai_parseport(&ai->qport, serv, hints))) goto error; ai->port = ai->qport; /* * FIXME: If an explicit A or AAAA record type conflicts with * .ai_family or with resconf.family (i.e. AAAA specified but * AF_INET6 not in interection of .ai_family and resconf.family), * then what? */ switch (ai->qtype) { case DNS_T_A: ai->af.todo = DNS_AI_AF2INDEX(AF_INET); break; case DNS_T_AAAA: ai->af.todo = DNS_AI_AF2INDEX(AF_INET6); break; default: /* 0, MX, SRV, etc */ switch (ai->hints.ai_family) { case AF_UNSPEC: ai->af.todo = DNS_AI_AF2INDEX(AF_INET) | DNS_AI_AF2INDEX(AF_INET6); break; case AF_INET: ai->af.todo = DNS_AI_AF2INDEX(AF_INET); break; case AF_INET6: ai->af.todo = DNS_AI_AF2INDEX(AF_INET6); break; default: break; } } return ai; syerr: error = dns_syerr(); error: *_error = error; dns_ai_close(ai); dns_res_close(res); return NULL; } /* dns_ai_open() */ void dns_ai_close(struct dns_addrinfo *ai) { if (!ai) return; dns_res_close(ai->res); dns_trace_close(ai->trace); if (ai->answer != ai->glue) dns_p_free(ai->glue); dns_p_free(ai->answer); free(ai); } /* dns_ai_close() */ static int dns_ai_setent(struct addrinfo **ent, union dns_any *any, enum dns_type type, struct dns_addrinfo *ai) { union u { struct sockaddr_in sin; struct sockaddr_in6 sin6; struct sockaddr_storage ss; } addr; const char *cname; size_t clen; switch (type) { case DNS_T_A: memset(&addr.sin, '\0', sizeof addr.sin); addr.sin.sin_family = AF_INET; addr.sin.sin_port = htons(ai->port); memcpy(&addr.sin.sin_addr, any, sizeof addr.sin.sin_addr); break; case DNS_T_AAAA: memset(&addr.sin6, '\0', sizeof addr.sin6); addr.sin6.sin6_family = AF_INET6; addr.sin6.sin6_port = htons(ai->port); memcpy(&addr.sin6.sin6_addr, any, sizeof addr.sin6.sin6_addr); break; default: return EINVAL; } /* switch() */ if (ai->hints.ai_flags & AI_CANONNAME) { cname = (*ai->cname)? ai->cname : ai->qname; clen = strlen(cname); } else { cname = NULL; clen = 0; } if (!(*ent = malloc(sizeof **ent + dns_sa_len(&addr) + ((ai->hints.ai_flags & AI_CANONNAME)? clen + 1 : 0)))) return dns_syerr(); memset(*ent, '\0', sizeof **ent); (*ent)->ai_family = addr.ss.ss_family; (*ent)->ai_socktype = ai->hints.ai_socktype; (*ent)->ai_protocol = ai->hints.ai_protocol; (*ent)->ai_addr = memcpy((unsigned char *)*ent + sizeof **ent, &addr, dns_sa_len(&addr)); (*ent)->ai_addrlen = dns_sa_len(&addr); if (ai->hints.ai_flags & AI_CANONNAME) (*ent)->ai_canonname = memcpy((unsigned char *)*ent + sizeof **ent + dns_sa_len(&addr), cname, clen + 1); ai->found++; return 0; } /* dns_ai_setent() */ enum dns_ai_state { DNS_AI_S_INIT, DNS_AI_S_NEXTAF, DNS_AI_S_NUMERIC, DNS_AI_S_SUBMIT, DNS_AI_S_CHECK, DNS_AI_S_FETCH, DNS_AI_S_FOREACH_I, DNS_AI_S_INIT_G, DNS_AI_S_ITERATE_G, DNS_AI_S_FOREACH_G, DNS_AI_S_SUBMIT_G, DNS_AI_S_CHECK_G, DNS_AI_S_FETCH_G, DNS_AI_S_DONE, }; /* enum dns_ai_state */ #define dns_ai_goto(which) do { ai->state = (which); goto exec; } while (0) int dns_ai_nextent(struct addrinfo **ent, struct dns_addrinfo *ai) { struct dns_packet *ans, *glue; struct dns_rr rr; char qname[DNS_D_MAXNAME + 1]; union dns_any any; size_t qlen, clen; int error; *ent = 0; exec: switch (ai->state) { case DNS_AI_S_INIT: ai->state++; /* FALL THROUGH */ case DNS_AI_S_NEXTAF: if (!dns_ai_nextaf(ai)) dns_ai_goto(DNS_AI_S_DONE); ai->state++; /* FALL THROUGH */ case DNS_AI_S_NUMERIC: if (1 == dns_inet_pton(AF_INET, ai->qname, &any.a)) { if (ai->af.atype == AF_INET) { ai->state = DNS_AI_S_NEXTAF; return dns_ai_setent(ent, &any, DNS_T_A, ai); } else { dns_ai_goto(DNS_AI_S_NEXTAF); } } if (1 == dns_inet_pton(AF_INET6, ai->qname, &any.aaaa)) { if (ai->af.atype == AF_INET6) { ai->state = DNS_AI_S_NEXTAF; return dns_ai_setent(ent, &any, DNS_T_AAAA, ai); } else { dns_ai_goto(DNS_AI_S_NEXTAF); } } if (ai->hints.ai_flags & AI_NUMERICHOST) dns_ai_goto(DNS_AI_S_NEXTAF); ai->state++; /* FALL THROUGH */ case DNS_AI_S_SUBMIT: assert(ai->res); if ((error = dns_res_submit(ai->res, ai->qname, dns_ai_qtype(ai), DNS_C_IN))) return error; ai->state++; /* FALL THROUGH */ case DNS_AI_S_CHECK: if ((error = dns_res_check(ai->res))) return error; ai->state++; /* FALL THROUGH */ case DNS_AI_S_FETCH: if (!(ans = dns_res_fetch_and_study(ai->res, &error))) return error; if (ai->glue != ai->answer) dns_p_free(ai->glue); ai->glue = dns_p_movptr(&ai->answer, &ans); /* Search generator may have changed the qname. */ if (!(qlen = dns_d_expand(qname, sizeof qname, 12, ai->answer, &error))) return error; else if (qlen >= sizeof qname) return DNS_EILLEGAL; if (!dns_d_cname(ai->cname, sizeof ai->cname, qname, qlen, ai->answer, &error)) return error; dns_strlcpy(ai->i_cname, ai->cname, sizeof ai->i_cname); dns_rr_i_init(&ai->i); ai->i.section = DNS_S_AN; ai->i.name = ai->i_cname; ai->i.type = dns_ai_qtype(ai); ai->i.sort = &dns_rr_i_order; ai->state++; /* FALL THROUGH */ case DNS_AI_S_FOREACH_I: if (!dns_rr_grep(&rr, 1, &ai->i, ai->answer, &error)) dns_ai_goto(DNS_AI_S_NEXTAF); if ((error = dns_any_parse(&any, &rr, ai->answer))) return error; ai->port = ai->qport; switch (rr.type) { case DNS_T_A: case DNS_T_AAAA: return dns_ai_setent(ent, &any, rr.type, ai); default: if (!(clen = dns_any_cname(ai->cname, sizeof ai->cname, &any, rr.type))) dns_ai_goto(DNS_AI_S_FOREACH_I); /* * Find the "real" canonical name. Some authorities * publish aliases where an RFC defines a canonical * name. We trust that the resolver followed any * CNAME chains on it's own, regardless of whether * the "smart" option is enabled. */ if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->cname, clen, ai->answer, &error)) return error; if (rr.type == DNS_T_SRV) ai->port = any.srv.port; break; } /* switch() */ ai->state++; /* FALL THROUGH */ case DNS_AI_S_INIT_G: ai->g_depth = 0; ai->state++; /* FALL THROUGH */ case DNS_AI_S_ITERATE_G: dns_strlcpy(ai->g_cname, ai->cname, sizeof ai->g_cname); dns_rr_i_init(&ai->g); ai->g.section = DNS_S_ALL & ~DNS_S_QD; ai->g.name = ai->g_cname; ai->g.type = ai->af.qtype; ai->state++; /* FALL THROUGH */ case DNS_AI_S_FOREACH_G: if (!dns_rr_grep(&rr, 1, &ai->g, ai->glue, &error)) { if (dns_rr_i_count(&ai->g) > 0) dns_ai_goto(DNS_AI_S_FOREACH_I); else dns_ai_goto(DNS_AI_S_SUBMIT_G); } if ((error = dns_any_parse(&any, &rr, ai->glue))) return error; return dns_ai_setent(ent, &any, rr.type, ai); case DNS_AI_S_SUBMIT_G: { struct dns_rr_i I_instance = { 0 }; I_instance.section = DNS_S_QD; I_instance.name = ai->g.name; I_instance.type = ai->g.type; /* skip if already queried */ if (dns_rr_grep(&rr, 1, &I_instance, ai->glue, &error)) dns_ai_goto(DNS_AI_S_FOREACH_I); /* skip if we recursed (CNAME chains should have been handled in the resolver) */ if (++ai->g_depth > 1) dns_ai_goto(DNS_AI_S_FOREACH_I); if ((error = dns_res_submit(ai->res, ai->g.name, ai->g.type, DNS_C_IN))) return error; ai->state++; } /* FALL THROUGH */ case DNS_AI_S_CHECK_G: if ((error = dns_res_check(ai->res))) return error; ai->state++; /* FALL THROUGH */ case DNS_AI_S_FETCH_G: if (!(ans = dns_res_fetch_and_study(ai->res, &error))) return error; glue = dns_p_merge(ai->glue, DNS_S_ALL, ans, DNS_S_ALL, &error); dns_p_setptr(&ans, NULL); if (!glue) return error; if (ai->glue != ai->answer) dns_p_free(ai->glue); ai->glue = glue; if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->g.name, strlen(ai->g.name), ai->glue, &error)) dns_ai_goto(DNS_AI_S_FOREACH_I); dns_ai_goto(DNS_AI_S_ITERATE_G); case DNS_AI_S_DONE: if (ai->found) { return ENOENT; /* TODO: Just return 0 */ } else if (ai->answer) { switch (dns_p_rcode(ai->answer)) { case DNS_RC_NOERROR: /* FALL THROUGH */ case DNS_RC_NXDOMAIN: return DNS_ENONAME; default: return DNS_EFAIL; } } else { return DNS_EFAIL; } default: return EINVAL; } /* switch() */ } /* dns_ai_nextent() */ time_t dns_ai_elapsed(struct dns_addrinfo *ai) { return (ai->res)? dns_res_elapsed(ai->res) : 0; } /* dns_ai_elapsed() */ void dns_ai_clear(struct dns_addrinfo *ai) { if (ai->res) dns_res_clear(ai->res); } /* dns_ai_clear() */ int dns_ai_events(struct dns_addrinfo *ai) { return (ai->res)? dns_res_events(ai->res) : 0; } /* dns_ai_events() */ int dns_ai_pollfd(struct dns_addrinfo *ai) { return (ai->res)? dns_res_pollfd(ai->res) : -1; } /* dns_ai_pollfd() */ time_t dns_ai_timeout(struct dns_addrinfo *ai) { return (ai->res)? dns_res_timeout(ai->res) : 0; } /* dns_ai_timeout() */ int dns_ai_poll(struct dns_addrinfo *ai, int timeout) { return (ai->res)? dns_res_poll(ai->res, timeout) : 0; } /* dns_ai_poll() */ size_t dns_ai_print(void *_dst, size_t lim, struct addrinfo *ent, struct dns_addrinfo *ai) { struct dns_buf dst = DNS_B_INTO(_dst, lim); char addr[DNS_PP_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) + 1]; char __dst[DNS_STRMAXLEN + 1] = { 0 }; dns_b_puts(&dst, "[ "); dns_b_puts(&dst, ai->qname); dns_b_puts(&dst, " IN "); if (ai->qtype) { dns_b_puts(&dst, dns_strtype(ai->qtype, __dst)); } else if (ent->ai_family == AF_INET) { dns_b_puts(&dst, dns_strtype(DNS_T_A, __dst)); } else if (ent->ai_family == AF_INET6) { dns_b_puts(&dst, dns_strtype(DNS_T_AAAA, __dst)); } else { dns_b_puts(&dst, "0"); } dns_b_puts(&dst, " ]\n"); dns_b_puts(&dst, ".ai_family = "); switch (ent->ai_family) { case AF_INET: dns_b_puts(&dst, "AF_INET"); break; case AF_INET6: dns_b_puts(&dst, "AF_INET6"); break; default: dns_b_fmtju(&dst, ent->ai_family, 0); break; } dns_b_putc(&dst, '\n'); dns_b_puts(&dst, ".ai_socktype = "); switch (ent->ai_socktype) { case SOCK_STREAM: dns_b_puts(&dst, "SOCK_STREAM"); break; case SOCK_DGRAM: dns_b_puts(&dst, "SOCK_DGRAM"); break; default: dns_b_fmtju(&dst, ent->ai_socktype, 0); break; } dns_b_putc(&dst, '\n'); dns_inet_ntop(dns_sa_family(ent->ai_addr), dns_sa_addr(dns_sa_family(ent->ai_addr), ent->ai_addr, NULL), addr, sizeof addr); dns_b_puts(&dst, ".ai_addr = ["); dns_b_puts(&dst, addr); dns_b_puts(&dst, "]:"); dns_b_fmtju(&dst, ntohs(*dns_sa_port(dns_sa_family(ent->ai_addr), ent->ai_addr)), 0); dns_b_putc(&dst, '\n'); dns_b_puts(&dst, ".ai_canonname = "); dns_b_puts(&dst, (ent->ai_canonname)? ent->ai_canonname : "[NULL]"); dns_b_putc(&dst, '\n'); return dns_b_strllen(&dst); } /* dns_ai_print() */ const struct dns_stat *dns_ai_stat(struct dns_addrinfo *ai) { return (ai->res)? dns_res_stat(ai->res) : &ai->st; } /* dns_ai_stat() */ struct dns_trace *dns_ai_trace(struct dns_addrinfo *ai) { return ai->trace; } /* dns_ai_trace() */ void dns_ai_settrace(struct dns_addrinfo *ai, struct dns_trace *trace) { struct dns_trace *otrace = ai->trace; ai->trace = dns_trace_acquire_p(trace); dns_trace_close(otrace); if (ai->res) dns_res_settrace(ai->res, trace); } /* dns_ai_settrace() */ /* * M I S C E L L A N E O U S R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ static const struct { char name[16]; enum dns_section type; } dns_sections[] = { { "QUESTION", DNS_S_QUESTION }, { "QD", DNS_S_QUESTION }, { "ANSWER", DNS_S_ANSWER }, { "AN", DNS_S_ANSWER }, { "AUTHORITY", DNS_S_AUTHORITY }, { "NS", DNS_S_AUTHORITY }, { "ADDITIONAL", DNS_S_ADDITIONAL }, { "AR", DNS_S_ADDITIONAL }, }; const char *(dns_strsection)(enum dns_section section, char *_dst) { struct dns_buf dst = DNS_B_INTO(_dst, DNS_STRMAXLEN + 1); unsigned i; for (i = 0; i < lengthof(dns_sections); i++) { if (dns_sections[i].type & section) { dns_b_puts(&dst, dns_sections[i].name); section &= ~dns_sections[i].type; if (section) dns_b_putc(&dst, '|'); } } if (section || dst.p == dst.base) dns_b_fmtju(&dst, (0xffff & section), 0); return dns_b_tostring(&dst); } /* dns_strsection() */ enum dns_section dns_isection(const char *src) { enum dns_section section = 0; char sbuf[128]; char *name, *next; unsigned i; dns_strlcpy(sbuf, src, sizeof sbuf); next = sbuf; while ((name = dns_strsep(&next, "|+, \t"))) { for (i = 0; i < lengthof(dns_sections); i++) { if (!strcasecmp(dns_sections[i].name, name)) { section |= dns_sections[i].type; break; } } } return section; } /* dns_isection() */ static const struct { char name[8]; enum dns_class type; } dns_classes[] = { { "IN", DNS_C_IN }, }; const char *(dns_strclass)(enum dns_class type, char *_dst) { struct dns_buf dst = DNS_B_INTO(_dst, DNS_STRMAXLEN + 1); unsigned i; for (i = 0; i < lengthof(dns_classes); i++) { if (dns_classes[i].type == type) { dns_b_puts(&dst, dns_classes[i].name); break; } } if (dst.p == dst.base) dns_b_fmtju(&dst, (0xffff & type), 0); return dns_b_tostring(&dst); } /* dns_strclass() */ enum dns_class dns_iclass(const char *name) { unsigned i, class; for (i = 0; i < lengthof(dns_classes); i++) { if (!strcasecmp(dns_classes[i].name, name)) return dns_classes[i].type; } class = 0; while (dns_isdigit(*name)) { class *= 10; class += *name++ - '0'; } return DNS_PP_MIN(class, 0xffff); } /* dns_iclass() */ const char *(dns_strtype)(enum dns_type type, char *_dst) { struct dns_buf dst = DNS_B_INTO(_dst, DNS_STRMAXLEN + 1); unsigned i; for (i = 0; i < lengthof(dns_rrtypes); i++) { if (dns_rrtypes[i].type == type) { dns_b_puts(&dst, dns_rrtypes[i].name); break; } } if (dst.p == dst.base) dns_b_fmtju(&dst, (0xffff & type), 0); return dns_b_tostring(&dst); } /* dns_strtype() */ enum dns_type dns_itype(const char *name) { unsigned i, type; for (i = 0; i < lengthof(dns_rrtypes); i++) { if (!strcasecmp(dns_rrtypes[i].name, name)) return dns_rrtypes[i].type; } type = 0; while (dns_isdigit(*name)) { type *= 10; type += *name++ - '0'; } return DNS_PP_MIN(type, 0xffff); } /* dns_itype() */ static char dns_opcodes[16][16] = { [DNS_OP_QUERY] = "QUERY", [DNS_OP_IQUERY] = "IQUERY", [DNS_OP_STATUS] = "STATUS", [DNS_OP_NOTIFY] = "NOTIFY", [DNS_OP_UPDATE] = "UPDATE", }; static const char *dns__strcode(int code, volatile char *dst, size_t lim) { char _tmp[48] = ""; struct dns_buf tmp; size_t p; assert(lim > 0); dns_b_fmtju(dns_b_into(&tmp, _tmp, DNS_PP_MIN(sizeof _tmp, lim - 1)), code, 0); /* copy downwards so first byte is copied last (see below) */ p = (size_t)(tmp.p - tmp.base); dst[p] = '\0'; while (p--) dst[p] = _tmp[p]; return (const char *)dst; } const char *dns_stropcode(enum dns_opcode opcode) { opcode = (unsigned)opcode % lengthof(dns_opcodes); if ('\0' == dns_opcodes[opcode][0]) return dns__strcode(opcode, dns_opcodes[opcode], sizeof dns_opcodes[opcode]); return dns_opcodes[opcode]; } /* dns_stropcode() */ enum dns_opcode dns_iopcode(const char *name) { unsigned opcode; for (opcode = 0; opcode < lengthof(dns_opcodes); opcode++) { if (!strcasecmp(name, dns_opcodes[opcode])) return opcode; } opcode = 0; while (dns_isdigit(*name)) { opcode *= 10; opcode += *name++ - '0'; } return DNS_PP_MIN(opcode, 0x0f); } /* dns_iopcode() */ static char dns_rcodes[32][16] = { [DNS_RC_NOERROR] = "NOERROR", [DNS_RC_FORMERR] = "FORMERR", [DNS_RC_SERVFAIL] = "SERVFAIL", [DNS_RC_NXDOMAIN] = "NXDOMAIN", [DNS_RC_NOTIMP] = "NOTIMP", [DNS_RC_REFUSED] = "REFUSED", [DNS_RC_YXDOMAIN] = "YXDOMAIN", [DNS_RC_YXRRSET] = "YXRRSET", [DNS_RC_NXRRSET] = "NXRRSET", [DNS_RC_NOTAUTH] = "NOTAUTH", [DNS_RC_NOTZONE] = "NOTZONE", /* EDNS(0) extended RCODEs ... */ [DNS_RC_BADVERS] = "BADVERS", }; const char *dns_strrcode(enum dns_rcode rcode) { rcode = (unsigned)rcode % lengthof(dns_rcodes); if ('\0' == dns_rcodes[rcode][0]) return dns__strcode(rcode, dns_rcodes[rcode], sizeof dns_rcodes[rcode]); return dns_rcodes[rcode]; } /* dns_strrcode() */ enum dns_rcode dns_ircode(const char *name) { unsigned rcode; for (rcode = 0; rcode < lengthof(dns_rcodes); rcode++) { if (!strcasecmp(name, dns_rcodes[rcode])) return rcode; } rcode = 0; while (dns_isdigit(*name)) { rcode *= 10; rcode += *name++ - '0'; } return DNS_PP_MIN(rcode, 0xfff); } /* dns_ircode() */ /* * C O M M A N D - L I N E / R E G R E S S I O N R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #if DNS_MAIN #include #include #include #include #if _WIN32 #include #endif #if !_WIN32 #include #endif struct { struct { const char *path[8]; unsigned count; } resconf, nssconf, hosts, cache; const char *qname; enum dns_type qtype; int (*sort)(); const char *trace; int verbose; struct { struct dns_resolv_conf *resconf; struct dns_hosts *hosts; struct dns_trace *trace; } memo; struct sockaddr_storage socks_host; const char *socks_user; const char *socks_password; } MAIN = { .sort = &dns_rr_i_packet, }; static void hexdump(const unsigned char *src, size_t len, FILE *fp) { struct dns_hxd_lines_i lines = { 0 }; char line[128]; while (dns_hxd_lines(line, sizeof line, src, len, &lines)) { fputs(line, fp); } } /* hexdump() */ DNS_NORETURN static void panic(const char *fmt, ...) { va_list ap; va_start(ap, fmt); #if _WIN32 vfprintf(stderr, fmt, ap); exit(EXIT_FAILURE); #else verrx(EXIT_FAILURE, fmt, ap); #endif } /* panic() */ #define panic_(fn, ln, fmt, ...) \ panic(fmt "%0s", (fn), (ln), __VA_ARGS__) #define panic(...) \ panic_(__func__, __LINE__, "(%s:%d) " __VA_ARGS__, "") static void *grow(unsigned char *p, size_t size) { void *tmp; if (!(tmp = realloc(p, size))) panic("realloc(%"PRIuZ"): %s", size, dns_strerror(errno)); return tmp; } /* grow() */ static size_t add(size_t a, size_t b) { if (~a < b) panic("%"PRIuZ" + %"PRIuZ": integer overflow", a, b); return a + b; } /* add() */ static size_t append(unsigned char **dst, size_t osize, const void *src, size_t len) { size_t size = add(osize, len); *dst = grow(*dst, size); memcpy(*dst + osize, src, len); return size; } /* append() */ static size_t slurp(unsigned char **dst, size_t osize, FILE *fp, const char *path) { size_t size = osize; unsigned char buf[1024]; size_t count; while ((count = fread(buf, 1, sizeof buf, fp))) size = append(dst, size, buf, count); if (ferror(fp)) panic("%s: %s", path, dns_strerror(errno)); return size; } /* slurp() */ static struct dns_resolv_conf *resconf(void) { struct dns_resolv_conf **resconf = &MAIN.memo.resconf; const char *path; unsigned i; int error; if (*resconf) return *resconf; if (!(*resconf = dns_resconf_open(&error))) panic("dns_resconf_open: %s", dns_strerror(error)); if (!MAIN.resconf.count) MAIN.resconf.path[MAIN.resconf.count++] = "/etc/resolv.conf"; for (i = 0; i < MAIN.resconf.count; i++) { path = MAIN.resconf.path[i]; if (0 == strcmp(path, "-")) error = dns_resconf_loadfile(*resconf, stdin); else error = dns_resconf_loadpath(*resconf, path); if (error) panic("%s: %s", path, dns_strerror(error)); } for (i = 0; i < MAIN.nssconf.count; i++) { path = MAIN.nssconf.path[i]; if (0 == strcmp(path, "-")) error = dns_nssconf_loadfile(*resconf, stdin); else error = dns_nssconf_loadpath(*resconf, path); if (error) panic("%s: %s", path, dns_strerror(error)); } if (!MAIN.nssconf.count) { path = "/etc/nsswitch.conf"; if (!(error = dns_nssconf_loadpath(*resconf, path))) MAIN.nssconf.path[MAIN.nssconf.count++] = path; else if (error != ENOENT) panic("%s: %s", path, dns_strerror(error)); } return *resconf; } /* resconf() */ static struct dns_hosts *hosts(void) { struct dns_hosts **hosts = &MAIN.memo.hosts; const char *path; unsigned i; int error; if (*hosts) return *hosts; if (!MAIN.hosts.count) { MAIN.hosts.path[MAIN.hosts.count++] = "/etc/hosts"; /* Explicitly test dns_hosts_local() */ if (!(*hosts = dns_hosts_local(&error))) panic("%s: %s", "/etc/hosts", dns_strerror(error)); return *hosts; } if (!(*hosts = dns_hosts_open(&error))) panic("dns_hosts_open: %s", dns_strerror(error)); for (i = 0; i < MAIN.hosts.count; i++) { path = MAIN.hosts.path[i]; if (0 == strcmp(path, "-")) error = dns_hosts_loadfile(*hosts, stdin); else error = dns_hosts_loadpath(*hosts, path); if (error) panic("%s: %s", path, dns_strerror(error)); } return *hosts; } /* hosts() */ #if DNS_CACHE #include "cache.h" static struct dns_cache *cache(void) { static struct cache *cache; const char *path; unsigned i; int error; if (cache) return cache_resi(cache); if (!MAIN.cache.count) return NULL; if (!(cache = cache_open(&error))) panic("%s: %s", MAIN.cache.path[0], dns_strerror(error)); for (i = 0; i < MAIN.cache.count; i++) { path = MAIN.cache.path[i]; if (!strcmp(path, "-")) { if ((error = cache_loadfile(cache, stdin, NULL, 0))) panic("%s: %s", path, dns_strerror(error)); } else if ((error = cache_loadpath(cache, path, NULL, 0))) panic("%s: %s", path, dns_strerror(error)); } return cache_resi(cache); } /* cache() */ #else static struct dns_cache *cache(void) { return NULL; } #endif static struct dns_trace *trace(const char *mode) { static char omode[64] = ""; struct dns_trace **trace = &MAIN.memo.trace; FILE *fp; int error; if (*trace && 0 == strcmp(omode, mode)) return *trace; if (!MAIN.trace) return NULL; if (!(fp = fopen(MAIN.trace, mode))) panic("%s: %s", MAIN.trace, strerror(errno)); dns_trace_close(*trace); if (!(*trace = dns_trace_open(fp, &error))) panic("%s: %s", MAIN.trace, dns_strerror(error)); dns_strlcpy(omode, mode, sizeof omode); return *trace; } static void print_packet(struct dns_packet *P, FILE *fp) { struct dns_rr_i I_instance = { 0 }; I.sort = MAIN.sort; dns_p_dump3(P, &I, fp); if (MAIN.verbose > 2) hexdump(P->data, P->end, fp); } /* print_packet() */ static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 }; union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } Q_instance = { 0 }; struct dns_packet *P = dns_p_init(&P_instance.p, 512); struct dns_packet *Q = dns_p_init(&Q_instance.p, 512); enum dns_section section; struct dns_rr rr; int error; union dns_any any; char pretty[sizeof any * 2]; size_t len; char __dst[DNS_STRMAXLEN + 1] = { 0 }; P->end = fread(P->data, 1, P->size, stdin); fputs(";; [HEADER]\n", stdout); fprintf(stdout, ";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr); fprintf(stdout, ";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode); fprintf(stdout, ";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa); fprintf(stdout, ";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc); fprintf(stdout, ";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd); fprintf(stdout, ";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra); fprintf(stdout, ";; rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P)); section = 0; dns_rr_foreach(&rr, P, .sort = MAIN.sort) { if (section != rr.section) fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section, __dst), dns_p_count(P, rr.section)); if ((len = dns_rr_print(pretty, sizeof pretty, &rr, P, &error))) fprintf(stdout, "%s\n", pretty); dns_rr_copy(Q, &rr, P); section = rr.section; } fputs("; ; ; ; ; ; ; ;\n\n", stdout); section = 0; #if 0 dns_rr_foreach(&rr, Q, .name = "ns8.yahoo.com.") { #else char _p[DNS_D_MAXNAME + 1] = { 0 }; const char *dn = "ns8.yahoo.com"; char *_name = dns_d_init(_p, sizeof _p, dn, strlen (dn), DNS_D_ANCHOR); struct dns_rr rrset[32]; struct dns_rr_i I_instance = { 0 }; struct dns_rr_i *rri = &I; unsigned rrcount = dns_rr_grep(rrset, lengthof(rrset), rri, Q, &error); I.name = _name; I.sort = MAIN.sort; for (unsigned i = 0; i < rrcount; i++) { rr = rrset[i]; #endif if (section != rr.section) fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section, __dst), dns_p_count(Q, rr.section)); if ((len = dns_rr_print(pretty, sizeof pretty, &rr, Q, &error))) fprintf(stdout, "%s\n", pretty); section = rr.section; } if (MAIN.verbose > 1) { fprintf(stderr, "orig:%"PRIuZ"\n", P->end); hexdump(P->data, P->end, stdout); fprintf(stderr, "copy:%"PRIuZ"\n", Q->end); hexdump(Q->data, Q->end, stdout); } return 0; } /* parse_packet() */ static int parse_domain(int argc, char *argv[]) { char _p[DNS_D_MAXNAME + 1] = { 0 }; char *dn; dn = (argc > 1)? argv[1] : "f.l.google.com"; printf("[%s]\n", dn); dn = dns_d_init(_p, sizeof _p, dn, strlen (dn), DNS_D_ANCHOR); do { puts(dn); } while (dns_d_cleave(dn, strlen(dn) + 1, dn, strlen(dn))); return 0; } /* parse_domain() */ static int trim_domain(int argc, char **argv) { for (argc--, argv++; argc > 0; argc--, argv++) { char name[DNS_D_MAXNAME + 1]; dns_d_trim(name, sizeof name, *argv, strlen(*argv), DNS_D_ANCHOR); puts(name); } return 0; } /* trim_domain() */ static int expand_domain(int argc, char *argv[]) { unsigned short rp = 0; unsigned char *src = NULL; unsigned char *dst; struct dns_packet *pkt; size_t lim = 0, len; int error; if (argc > 1) rp = atoi(argv[1]); len = slurp(&src, 0, stdin, "-"); if (!(pkt = dns_p_make(len, &error))) panic("malloc(%"PRIuZ"): %s", len, dns_strerror(error)); memcpy(pkt->data, src, len); pkt->end = len; lim = 1; dst = grow(NULL, lim); while (lim <= (len = dns_d_expand(dst, lim, rp, pkt, &error))) { lim = add(len, 1); dst = grow(dst, lim); } if (!len) panic("expand: %s", dns_strerror(error)); fwrite(dst, 1, len, stdout); fflush(stdout); free(src); free(dst); free(pkt); return 0; } /* expand_domain() */ static int show_resconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { unsigned i; resconf(); /* load it */ fputs("; SOURCES\n", stdout); for (i = 0; i < MAIN.resconf.count; i++) fprintf(stdout, "; %s\n", MAIN.resconf.path[i]); for (i = 0; i < MAIN.nssconf.count; i++) fprintf(stdout, "; %s\n", MAIN.nssconf.path[i]); fputs(";\n", stdout); dns_resconf_dump(resconf(), stdout); return 0; } /* show_resconf() */ static int show_nssconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { unsigned i; resconf(); fputs("# SOURCES\n", stdout); for (i = 0; i < MAIN.resconf.count; i++) fprintf(stdout, "# %s\n", MAIN.resconf.path[i]); for (i = 0; i < MAIN.nssconf.count; i++) fprintf(stdout, "# %s\n", MAIN.nssconf.path[i]); fputs("#\n", stdout); dns_nssconf_dump(resconf(), stdout); return 0; } /* show_nssconf() */ static int show_hosts(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { unsigned i; hosts(); fputs("# SOURCES\n", stdout); for (i = 0; i < MAIN.hosts.count; i++) fprintf(stdout, "# %s\n", MAIN.hosts.path[i]); fputs("#\n", stdout); dns_hosts_dump(hosts(), stdout); return 0; } /* show_hosts() */ static int query_hosts(int argc, char *argv[]) { union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } Q_instance = { 0 }; struct dns_packet *Q = dns_p_init(&Q_instance.p, 512); struct dns_packet *A; char qname[DNS_D_MAXNAME + 1]; size_t qlen; int error; if (!MAIN.qname) MAIN.qname = (argc > 1)? argv[1] : "localhost"; if (!MAIN.qtype) MAIN.qtype = DNS_T_A; hosts(); if (MAIN.qtype == DNS_T_PTR && !strstr(MAIN.qname, "arpa")) { union { struct in_addr a; struct in6_addr a6; } addr; int af = (strchr(MAIN.qname, ':'))? AF_INET6 : AF_INET; if ((error = dns_pton(af, MAIN.qname, &addr))) panic("%s: %s", MAIN.qname, dns_strerror(error)); qlen = dns_ptr_qname(qname, sizeof qname, af, &addr); } else qlen = dns_strlcpy(qname, MAIN.qname, sizeof qname); if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, MAIN.qtype, DNS_C_IN, 0, 0))) panic("%s: %s", qname, dns_strerror(error)); if (!(A = dns_hosts_query(hosts(), Q, &error))) panic("%s: %s", qname, dns_strerror(error)); print_packet(A, stdout); free(A); return 0; } /* query_hosts() */ static int search_list(int argc, char *argv[]) { const char *qname = (argc > 1)? argv[1] : "f.l.google.com"; unsigned long i = 0; char name[DNS_D_MAXNAME + 1]; printf("[%s]\n", qname); while (dns_resconf_search(name, sizeof name, qname, strlen(qname), resconf(), &i)) puts(name); return 0; } /* search_list() */ static int permute_set(int argc, char *argv[]) { unsigned lo, hi, i; struct dns_k_permutor p; hi = (--argc > 0)? atoi(argv[argc]) : 8; lo = (--argc > 0)? atoi(argv[argc]) : 0; fprintf(stderr, "[%u .. %u]\n", lo, hi); dns_k_permutor_init(&p, lo, hi); for (i = lo; i <= hi; i++) fprintf(stdout, "%u\n", dns_k_permutor_step(&p)); // printf("%u -> %u -> %u\n", i, dns_k_permutor_E(&p, i), dns_k_permutor_D(&p, dns_k_permutor_E(&p, i))); return 0; } /* permute_set() */ static int shuffle_16(int argc, char *argv[]) { unsigned n, r; if (--argc > 0) { n = 0xffff & atoi(argv[argc]); r = (--argc > 0)? (unsigned)atoi(argv[argc]) : dns_random(); fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r)); } else { r = dns_random(); for (n = 0; n < 65536; n++) fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r)); } return 0; } /* shuffle_16() */ static int dump_random(int argc, char *argv[]) { unsigned char b[32]; unsigned i, j, n, r; n = (argc > 1)? atoi(argv[1]) : 32; while (n) { i = 0; do { r = dns_random(); for (j = 0; j < sizeof r && i < n && i < sizeof b; i++, j++) { b[i] = 0xff & r; r >>= 8; } } while (i < n && i < sizeof b); hexdump(b, i, stdout); n -= i; } return 0; } /* dump_random() */ static int send_query(int argc, char *argv[]) { union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } Q_instance = { 0 }; struct dns_packet *A, *Q = dns_p_init(&Q_instance.p, 512); char host[INET6_ADDRSTRLEN + 1]; struct sockaddr_storage ss; struct dns_socket *so; int error, type; struct dns_options opts = { 0 }; char __dst[DNS_STRMAXLEN + 1] = { 0 }; memset(&ss, 0, sizeof ss); if (argc > 1) { ss.ss_family = (strchr(argv[1], ':'))? AF_INET6 : AF_INET; if ((error = dns_pton(ss.ss_family, argv[1], dns_sa_addr(ss.ss_family, &ss, NULL)))) panic("%s: %s", argv[1], dns_strerror(error)); *dns_sa_port(ss.ss_family, &ss) = htons(53); } else memcpy(&ss, &resconf()->nameserver[0], dns_sa_len(&resconf()->nameserver[0])); if (!dns_inet_ntop(ss.ss_family, dns_sa_addr(ss.ss_family, &ss, NULL), host, sizeof host)) panic("bad host address, or none provided"); if (!MAIN.qname) MAIN.qname = "ipv6.google.com"; if (!MAIN.qtype) MAIN.qtype = DNS_T_AAAA; if ((error = dns_p_push(Q, DNS_S_QD, MAIN.qname, strlen(MAIN.qname), MAIN.qtype, DNS_C_IN, 0, 0))) panic("dns_p_push: %s", dns_strerror(error)); dns_header(Q)->rd = 1; if (strstr(argv[0], "udp")) type = SOCK_DGRAM; else if (strstr(argv[0], "tcp")) type = SOCK_STREAM; else type = dns_res_tcp2type(resconf()->options.tcp); fprintf(stderr, "querying %s for %s IN %s\n", host, MAIN.qname, dns_strtype(MAIN.qtype, __dst)); if (!(so = dns_so_open((struct sockaddr *)&resconf()->iface, type, &opts, &error))) panic("dns_so_open: %s", dns_strerror(error)); while (!(A = dns_so_query(so, Q, (struct sockaddr *)&ss, &error))) { if (error != DNS_EAGAIN) panic("dns_so_query: %s (%d)", dns_strerror(error), error); if (dns_so_elapsed(so) > 10) panic("query timed-out"); dns_so_poll(so, 1); } print_packet(A, stdout); dns_so_close(so); return 0; } /* send_query() */ static int print_arpa(int argc, char *argv[]) { const char *ip = (argc > 1)? argv[1] : "::1"; int af = (strchr(ip, ':'))? AF_INET6 : AF_INET; union { struct in_addr a4; struct in6_addr a6; } addr; char host[DNS_D_MAXNAME + 1]; if (1 != dns_inet_pton(af, ip, &addr) || 0 == dns_ptr_qname(host, sizeof host, af, &addr)) panic("%s: invalid address", ip); fprintf(stdout, "%s\n", host); return 0; } /* print_arpa() */ static int show_hints(int argc, char *argv[]) { struct dns_hints *(*load)(struct dns_resolv_conf *, int *); const char *which, *how, *who; struct dns_hints *hints; int error; which = (argc > 1)? argv[1] : "local"; how = (argc > 2)? argv[2] : "plain"; who = (argc > 3)? argv[3] : "google.com"; load = (0 == strcmp(which, "local")) ? &dns_hints_local : &dns_hints_root; if (!(hints = load(resconf(), &error))) panic("%s: %s", argv[0], dns_strerror(error)); if (0 == strcmp(how, "plain")) { dns_hints_dump(hints, stdout); } else { union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 }; struct dns_packet *query, *answer; query = dns_p_init(&P_instance.p, 512); if ((error = dns_p_push(query, DNS_S_QUESTION, who, strlen(who), DNS_T_A, DNS_C_IN, 0, 0))) panic("%s: %s", who, dns_strerror(error)); if (!(answer = dns_hints_query(hints, query, &error))) panic("%s: %s", who, dns_strerror(error)); print_packet(answer, stdout); free(answer); } dns_hints_close(hints); return 0; } /* show_hints() */ static int resolve_query(int argc DNS_NOTUSED, char *argv[]) { _Bool recurse = !!strstr(argv[0], "recurse"); struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local; struct dns_resolver *R; struct dns_packet *ans; const struct dns_stat *st; int error; struct dns_options opts = { 0 }; opts.socks_host = &MAIN.socks_host; opts.socks_user = MAIN.socks_user; opts.socks_password = MAIN.socks_password; if (!MAIN.qname) MAIN.qname = "www.google.com"; if (!MAIN.qtype) MAIN.qtype = DNS_T_A; resconf()->options.recurse = recurse; if (!(R = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), &opts, &error))) panic("%s: %s", MAIN.qname, dns_strerror(error)); dns_res_settrace(R, trace("w+b")); if ((error = dns_res_submit(R, MAIN.qname, MAIN.qtype, DNS_C_IN))) panic("%s: %s", MAIN.qname, dns_strerror(error)); while ((error = dns_res_check(R))) { if (error != DNS_EAGAIN) panic("dns_res_check: %s (%d)", dns_strerror(error), error); if (dns_res_elapsed(R) > 30) panic("query timed-out"); dns_res_poll(R, 1); } ans = dns_res_fetch(R, &error); print_packet(ans, stdout); free(ans); st = dns_res_stat(R); putchar('\n'); printf(";; queries: %"PRIuZ"\n", st->queries); printf(";; udp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.sent.count, st->udp.sent.bytes); printf(";; udp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.rcvd.count, st->udp.rcvd.bytes); printf(";; tcp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.sent.count, st->tcp.sent.bytes); printf(";; tcp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.rcvd.count, st->tcp.rcvd.bytes); dns_res_close(R); return 0; } /* resolve_query() */ static int resolve_addrinfo(int argc DNS_NOTUSED, char *argv[]) { _Bool recurse = !!strstr(argv[0], "recurse"); struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local; struct dns_resolver *res = NULL; struct dns_addrinfo *ai = NULL; struct addrinfo ai_hints = { .ai_family = PF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_flags = AI_CANONNAME }; struct addrinfo *ent; char pretty[512]; int error; struct dns_options opts = { 0 }; if (!MAIN.qname) MAIN.qname = "www.google.com"; /* NB: MAIN.qtype of 0 means obey hints.ai_family */ resconf()->options.recurse = recurse; if (!(res = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), &opts, &error))) panic("%s: %s", MAIN.qname, dns_strerror(error)); if (!(ai = dns_ai_open(MAIN.qname, "80", MAIN.qtype, &ai_hints, res, &error))) panic("%s: %s", MAIN.qname, dns_strerror(error)); dns_ai_settrace(ai, trace("w+b")); do { switch (error = dns_ai_nextent(&ent, ai)) { case 0: dns_ai_print(pretty, sizeof pretty, ent, ai); fputs(pretty, stdout); free(ent); break; case ENOENT: break; case DNS_EAGAIN: if (dns_ai_elapsed(ai) > 30) panic("query timed-out"); dns_ai_poll(ai, 1); break; default: panic("dns_ai_nextent: %s (%d)", dns_strerror(error), error); } } while (error != ENOENT); dns_res_close(res); dns_ai_close(ai); return 0; } /* resolve_addrinfo() */ static int dump_trace(int argc DNS_NOTUSED, char *argv[]) { int error; if (!MAIN.trace) panic("no trace file specified"); if ((error = dns_trace_dump(trace("r"), stdout))) panic("dump_trace: %s", dns_strerror(error)); return 0; } /* dump_trace() */ static int echo_port(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { union { struct sockaddr sa; struct sockaddr_in sin; } port; int fd; memset(&port, 0, sizeof port); port.sin.sin_family = AF_INET; port.sin.sin_port = htons(5354); port.sin.sin_addr.s_addr = inet_addr("127.0.0.1"); if (-1 == (fd = socket(PF_INET, SOCK_DGRAM, 0))) panic("socket: %s", strerror(errno)); if (0 != bind(fd, &port.sa, sizeof port.sa)) panic("127.0.0.1:5353: %s", dns_strerror(errno)); for (;;) { union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 }; struct dns_packet *pkt = dns_p_init(&P_instance.p, 512); struct sockaddr_storage ss; socklen_t slen = sizeof ss; ssize_t count; #if defined(MSG_WAITALL) /* MinGW issue */ int rflags = MSG_WAITALL; #else int rflags = 0; #endif count = recvfrom(fd, (char *)pkt->data, pkt->size, rflags, (struct sockaddr *)&ss, &slen); if (!count || count < 0) panic("recvfrom: %s", strerror(errno)); pkt->end = count; dns_p_dump(pkt, stdout); (void)sendto(fd, (char *)pkt->data, pkt->end, 0, (struct sockaddr *)&ss, slen); } return 0; } /* echo_port() */ static int isection(int argc, char *argv[]) { char __dst[DNS_STRMAXLEN + 1] = { 0 }; const char *name = (argc > 1)? argv[1] : ""; int type; type = dns_isection(name); name = dns_strsection(type, __dst); printf("%s (%d)\n", name, type); return 0; } /* isection() */ static int iclass(int argc, char *argv[]) { char __dst[DNS_STRMAXLEN + 1] = { 0 }; const char *name = (argc > 1)? argv[1] : ""; int type; type = dns_iclass(name); name = dns_strclass(type, __dst); printf("%s (%d)\n", name, type); return 0; } /* iclass() */ static int itype(int argc, char *argv[]) { char __dst[DNS_STRMAXLEN + 1] = { 0 }; const char *name = (argc > 1)? argv[1] : ""; int type; type = dns_itype(name); name = dns_strtype(type, __dst); printf("%s (%d)\n", name, type); return 0; } /* itype() */ static int iopcode(int argc, char *argv[]) { const char *name = (argc > 1)? argv[1] : ""; int type; type = dns_iopcode(name); name = dns_stropcode(type); printf("%s (%d)\n", name, type); return 0; } /* iopcode() */ static int ircode(int argc, char *argv[]) { const char *name = (argc > 1)? argv[1] : ""; int type; type = dns_ircode(name); name = dns_strrcode(type); printf("%s (%d)\n", name, type); return 0; } /* ircode() */ #define SIZE1(x) { DNS_PP_STRINGIFY(x), sizeof (x) } #define SIZE2(x, ...) SIZE1(x), SIZE1(__VA_ARGS__) #define SIZE3(x, ...) SIZE1(x), SIZE2(__VA_ARGS__) #define SIZE4(x, ...) SIZE1(x), SIZE3(__VA_ARGS__) #define SIZE(...) DNS_PP_CALL(DNS_PP_XPASTE(SIZE, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) static int sizes(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { static const struct { const char *name; size_t size; } type[] = { SIZE(struct dns_header, struct dns_packet, struct dns_rr, struct dns_rr_i), SIZE(struct dns_a, struct dns_aaaa, struct dns_mx, struct dns_ns), SIZE(struct dns_cname, struct dns_soa, struct dns_ptr, struct dns_srv), SIZE(struct dns_sshfp, struct dns_txt, union dns_any), SIZE(struct dns_resolv_conf, struct dns_hosts, struct dns_hints, struct dns_hints_i), SIZE(struct dns_options, struct dns_socket, struct dns_resolver, struct dns_addrinfo), SIZE(struct dns_cache), SIZE(size_t), SIZE(void *), SIZE(long) }; unsigned i, max; for (i = 0, max = 0; i < lengthof(type); i++) max = DNS_PP_MAX(max, strlen(type[i].name)); for (i = 0; i < lengthof(type); i++) printf("%*s : %"PRIuZ"\n", max, type[i].name, type[i].size); return 0; } /* sizes() */ static const struct { const char *cmd; int (*run)(); const char *help; } cmds[] = { { "parse-packet", &parse_packet, "parse binary packet from stdin" }, { "parse-domain", &parse_domain, "anchor and iteratively cleave domain" }, { "trim-domain", &trim_domain, "trim and anchor domain name" }, { "expand-domain", &expand_domain, "expand domain at offset NN in packet from stdin" }, { "show-resconf", &show_resconf, "show resolv.conf data" }, { "show-hosts", &show_hosts, "show hosts data" }, { "show-nssconf", &show_nssconf, "show nsswitch.conf data" }, { "query-hosts", &query_hosts, "query A, AAAA or PTR in hosts data" }, { "search-list", &search_list, "generate query search list from domain" }, { "permute-set", &permute_set, "generate random permutation -> (0 .. N or N .. M)" }, { "shuffle-16", &shuffle_16, "simple 16-bit permutation" }, { "dump-random", &dump_random, "generate random bytes" }, { "send-query", &send_query, "send query to host" }, { "send-query-udp", &send_query, "send udp query to host" }, { "send-query-tcp", &send_query, "send tcp query to host" }, { "print-arpa", &print_arpa, "print arpa. zone name of address" }, { "show-hints", &show_hints, "print hints: show-hints [local|root] [plain|packet]" }, { "resolve-stub", &resolve_query, "resolve as stub resolver" }, { "resolve-recurse", &resolve_query, "resolve as recursive resolver" }, { "addrinfo-stub", &resolve_addrinfo, "resolve through getaddrinfo clone" }, { "addrinfo-recurse", &resolve_addrinfo, "resolve through getaddrinfo clone" }, /* { "resolve-nameinfo", &resolve_query, "resolve as recursive resolver" }, */ { "dump-trace", &dump_trace, "dump the contents of a trace file" }, { "echo", &echo_port, "server echo mode, for nmap fuzzing" }, { "isection", &isection, "parse section string" }, { "iclass", &iclass, "parse class string" }, { "itype", &itype, "parse type string" }, { "iopcode", &iopcode, "parse opcode string" }, { "ircode", &ircode, "parse rcode string" }, { "sizes", &sizes, "print data structure sizes" }, }; static void print_usage(const char *progname, FILE *fp) { static const char *usage = " [OPTIONS] COMMAND [ARGS]\n" " -c PATH Path to resolv.conf\n" " -n PATH Path to nsswitch.conf\n" " -l PATH Path to local hosts\n" " -z PATH Path to zone cache\n" " -q QNAME Query name\n" " -t QTYPE Query type\n" " -s HOW Sort records\n" " -S ADDR Address of SOCKS server to use\n" " -P PORT Port of SOCKS server to use\n" " -A USER:PASSWORD Credentials for the SOCKS server\n" " -f PATH Path to trace file\n" " -v Be more verbose (-vv show packets; -vvv hexdump packets)\n" " -V Print version info\n" " -h Print this usage message\n" "\n"; unsigned i, n, m; fputs(progname, fp); fputs(usage, fp); for (i = 0, m = 0; i < lengthof(cmds); i++) { if (strlen(cmds[i].cmd) > m) m = strlen(cmds[i].cmd); } for (i = 0; i < lengthof(cmds); i++) { fprintf(fp, " %s ", cmds[i].cmd); for (n = strlen(cmds[i].cmd); n < m; n++) putc(' ', fp); fputs(cmds[i].help, fp); putc('\n', fp); } fputs("\nReport bugs to William Ahern \n", fp); } /* print_usage() */ static void print_version(const char *progname, FILE *fp) { fprintf(fp, "%s (dns.c) %.8X\n", progname, dns_v_rel()); fprintf(fp, "vendor %s\n", dns_vendor()); fprintf(fp, "release %.8X\n", dns_v_rel()); fprintf(fp, "abi %.8X\n", dns_v_abi()); fprintf(fp, "api %.8X\n", dns_v_api()); } /* print_version() */ static void main_exit(void) { dns_trace_close(MAIN.memo.trace); MAIN.memo.trace = NULL; dns_hosts_close(MAIN.memo.hosts); MAIN.memo.hosts = NULL; dns_resconf_close(MAIN.memo.resconf); MAIN.memo.resconf = NULL; } /* main_exit() */ int main(int argc, char **argv) { extern int optind; extern char *optarg; const char *progname = argv[0]; unsigned i; int ch; atexit(&main_exit); while (-1 != (ch = getopt(argc, argv, "q:t:c:n:l:z:s:S:P:A:f:vVh"))) { switch (ch) { case 'c': assert(MAIN.resconf.count < lengthof(MAIN.resconf.path)); MAIN.resconf.path[MAIN.resconf.count++] = optarg; break; case 'n': assert(MAIN.nssconf.count < lengthof(MAIN.nssconf.path)); MAIN.nssconf.path[MAIN.nssconf.count++] = optarg; break; case 'l': assert(MAIN.hosts.count < lengthof(MAIN.hosts.path)); MAIN.hosts.path[MAIN.hosts.count++] = optarg; break; case 'z': assert(MAIN.cache.count < lengthof(MAIN.cache.path)); MAIN.cache.path[MAIN.cache.count++] = optarg; break; case 'q': MAIN.qname = optarg; break; case 't': for (i = 0; i < lengthof(dns_rrtypes); i++) { if (0 == strcasecmp(dns_rrtypes[i].name, optarg)) { MAIN.qtype = dns_rrtypes[i].type; break; } } if (MAIN.qtype) break; for (i = 0; dns_isdigit(optarg[i]); i++) { MAIN.qtype *= 10; MAIN.qtype += optarg[i] - '0'; } if (!MAIN.qtype) panic("%s: invalid query type", optarg); break; case 's': if (0 == strcasecmp(optarg, "packet")) MAIN.sort = &dns_rr_i_packet; else if (0 == strcasecmp(optarg, "shuffle")) MAIN.sort = &dns_rr_i_shuffle; else if (0 == strcasecmp(optarg, "order")) MAIN.sort = &dns_rr_i_order; else panic("%s: invalid sort method", optarg); break; case 'S': { dns_error_t error; struct dns_resolv_conf *conf = resconf(); conf->options.tcp = DNS_RESCONF_TCP_SOCKS; MAIN.socks_host.ss_family = (strchr(optarg, ':')) ? AF_INET6 : AF_INET; if ((error = dns_pton(MAIN.socks_host.ss_family, optarg, dns_sa_addr(MAIN.socks_host.ss_family, &MAIN.socks_host, NULL)))) panic("%s: %s", optarg, dns_strerror(error)); *dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(1080); break; } case 'P': if (! MAIN.socks_host.ss_family) panic("-P without prior -S"); *dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(atoi(optarg)); break; case 'A': { char *password; if (! MAIN.socks_host.ss_family) panic("-A without prior -S"); if (! (password = strchr(optarg, ':'))) panic("Usage: -A USER:PASSWORD"); *password = 0; password += 1; MAIN.socks_user = optarg; MAIN.socks_password = password; break; } case 'f': MAIN.trace = optarg; break; case 'v': dns_debug = ++MAIN.verbose; break; case 'V': print_version(progname, stdout); return 0; case 'h': print_usage(progname, stdout); return 0; default: print_usage(progname, stderr); return EXIT_FAILURE; } /* switch() */ } /* while() */ argc -= optind; argv += optind; for (i = 0; i < lengthof(cmds) && argv[0]; i++) { if (0 == strcmp(cmds[i].cmd, argv[0])) return cmds[i].run(argc, argv); } print_usage(progname, stderr); return EXIT_FAILURE; } /* main() */ #endif /* DNS_MAIN */ /* * pop file-scoped compiler annotations */ #if __clang__ #pragma clang diagnostic pop #elif DNS_GNUC_PREREQ(4,6,0) #pragma GCC diagnostic pop #endif