diff options
Diffstat (limited to 'src/tools')
39 files changed, 11520 insertions, 0 deletions
diff --git a/src/tools/analyzer/Makefile.am b/src/tools/analyzer/Makefile.am new file mode 100644 index 0000000..b40043d --- /dev/null +++ b/src/tools/analyzer/Makefile.am @@ -0,0 +1,22 @@ +sss_analyze_pythondir = $(libexecdir)/sssd + +dist_sss_analyze_python_SCRIPTS = \ + sss_analyze \ + $(NULL) + +pkgpythondir = $(python3dir)/sssd + +dist_pkgpython_DATA = \ + __init__.py \ + source_files.py \ + source_journald.py \ + source_reader.py \ + parser.py \ + sss_analyze.py \ + $(NULL) + +modulesdir = $(pkgpythondir)/modules +dist_modules_DATA = \ + modules/__init__.py \ + modules/request.py \ + $(NULL) diff --git a/src/tools/analyzer/Makefile.in b/src/tools/analyzer/Makefile.in new file mode 100644 index 0000000..0018403 --- /dev/null +++ b/src/tools/analyzer/Makefile.in @@ -0,0 +1,853 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/tools/analyzer +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/lib-ld.m4 \ + $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \ + $(top_srcdir)/version.m4 $(top_srcdir)/src/build_macros.m4 \ + $(top_srcdir)/src/external/platform.m4 \ + $(top_srcdir)/src/conf_macros.m4 \ + $(top_srcdir)/src/external/pkg.m4 \ + $(top_srcdir)/src/external/libpopt.m4 \ + $(top_srcdir)/src/external/libtalloc.m4 \ + $(top_srcdir)/src/external/libtdb.m4 \ + $(top_srcdir)/src/external/libtevent.m4 \ + $(top_srcdir)/src/external/libldb.m4 \ + $(top_srcdir)/src/external/libdhash.m4 \ + $(top_srcdir)/src/external/libini_config.m4 \ + $(top_srcdir)/src/external/libgssapi_krb5.m4 \ + $(top_srcdir)/src/external/pam.m4 \ + $(top_srcdir)/src/external/ldap.m4 \ + $(top_srcdir)/src/external/libpcre.m4 \ + $(top_srcdir)/src/external/krb5.m4 \ + $(top_srcdir)/src/external/libcares.m4 \ + $(top_srcdir)/src/external/libcmocka.m4 \ + $(top_srcdir)/src/external/docbook.m4 \ + $(top_srcdir)/src/external/sizes.m4 \ + $(top_srcdir)/src/external/python.m4 \ + $(top_srcdir)/src/external/selinux.m4 \ + $(top_srcdir)/src/external/crypto.m4 \ + $(top_srcdir)/src/external/nsupdate.m4 \ + $(top_srcdir)/src/external/libkeyutils.m4 \ + $(top_srcdir)/src/external/libkrad.m4 \ + $(top_srcdir)/src/external/libnl.m4 \ + $(top_srcdir)/src/external/systemd.m4 \ + $(top_srcdir)/src/external/pac_responder.m4 \ + $(top_srcdir)/src/external/cifsidmap.m4 \ + $(top_srcdir)/src/external/signal.m4 \ + $(top_srcdir)/src/external/inotify.m4 \ + $(top_srcdir)/src/external/samba.m4 \ + $(top_srcdir)/src/external/sasl.m4 \ + $(top_srcdir)/src/external/libnfsidmap.m4 \ + $(top_srcdir)/src/external/cwrap.m4 \ + $(top_srcdir)/src/external/libresolv.m4 \ + $(top_srcdir)/src/external/intgcheck.m4 \ + $(top_srcdir)/src/external/systemtap.m4 \ + $(top_srcdir)/src/external/service.m4 \ + $(top_srcdir)/src/external/test_ca.m4 \ + $(top_srcdir)/src/external/ax_valgrind_check.m4 \ + $(top_srcdir)/src/external/libjansson.m4 \ + $(top_srcdir)/src/external/libcurl.m4 \ + $(top_srcdir)/src/external/libjose.m4 \ + $(top_srcdir)/src/external/libuuid.m4 \ + $(top_srcdir)/src/external/libunistring.m4 \ + $(top_srcdir)/src/external/libpasskey.m4 \ + $(top_srcdir)/src/external/p11-kit.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(dist_sss_analyze_python_SCRIPTS) \ + $(dist_modules_DATA) $(dist_pkgpython_DATA) $(am__DIST_COMMON) +mkinstalldirs = $(SHELL) $(top_srcdir)/build/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(sss_analyze_pythondir)" \ + "$(DESTDIR)$(modulesdir)" "$(DESTDIR)$(pkgpythondir)" +SCRIPTS = $(dist_sss_analyze_python_SCRIPTS) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +DATA = $(dist_modules_DATA) $(dist_pkgpython_DATA) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build/mkinstalldirs +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CARES_CFLAGS = @CARES_CFLAGS@ +CARES_LIBS = @CARES_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CHECK_CFLAGS = @CHECK_CFLAGS@ +CHECK_LIBS = @CHECK_LIBS@ +CMOCKA_CFLAGS = @CMOCKA_CFLAGS@ +CMOCKA_LIBS = @CMOCKA_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPTO_CFLAGS = @CRYPTO_CFLAGS@ +CRYPTO_LIBS = @CRYPTO_LIBS@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CURL_CFLAGS = @CURL_CFLAGS@ +CURL_LIBS = @CURL_LIBS@ +CYGPATH_W = @CYGPATH_W@ +DBUS_CFLAGS = @DBUS_CFLAGS@ +DBUS_LIBS = @DBUS_LIBS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DHASH_CFLAGS = @DHASH_CFLAGS@ +DHASH_LIBS = @DHASH_LIBS@ +DLLTOOL = @DLLTOOL@ +DOCBOOK_XSLT = @DOCBOOK_XSLT@ +DOXYGEN = @DOXYGEN@ +DSYMUTIL = @DSYMUTIL@ +DTRACE = @DTRACE@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENABLE_VALGRIND_drd = @ENABLE_VALGRIND_drd@ +ENABLE_VALGRIND_helgrind = @ENABLE_VALGRIND_helgrind@ +ENABLE_VALGRIND_memcheck = @ENABLE_VALGRIND_memcheck@ +ENABLE_VALGRIND_sgcheck = @ENABLE_VALGRIND_sgcheck@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +FAKETIME = @FAKETIME@ +FGREP = @FGREP@ +FIDO2_CFLAGS = @FIDO2_CFLAGS@ +FIDO2_LIBS = @FIDO2_LIBS@ +FILECMD = @FILECMD@ +GDM_PAM_EXTENSIONS_CFLAGS = @GDM_PAM_EXTENSIONS_CFLAGS@ +GDM_PAM_EXTENSIONS_LIBS = @GDM_PAM_EXTENSIONS_LIBS@ +GMSGFMT = @GMSGFMT@ +GPO_DEFAULT = @GPO_DEFAULT@ +GREP = @GREP@ +GSSAPI_KRB5_CFLAGS = @GSSAPI_KRB5_CFLAGS@ +GSSAPI_KRB5_LIBS = @GSSAPI_KRB5_LIBS@ +HAVE_FAKEROOT = @HAVE_FAKEROOT@ +HAVE_LDAPMODIFY = @HAVE_LDAPMODIFY@ +HAVE_MANPAGES = @HAVE_MANPAGES@ +HAVE_NSS_WRAPPER = @HAVE_NSS_WRAPPER@ +HAVE_PAM_WRAPPER = @HAVE_PAM_WRAPPER@ +HAVE_PYTHON2 = @HAVE_PYTHON2@ +HAVE_PYTHON2_BINDINGS = @HAVE_PYTHON2_BINDINGS@ +HAVE_PYTHON3 = @HAVE_PYTHON3@ +HAVE_PYTHON3_BINDINGS = @HAVE_PYTHON3_BINDINGS@ +HAVE_SELINUX = @HAVE_SELINUX@ +HAVE_SEMANAGE = @HAVE_SEMANAGE@ +HAVE_UID_WRAPPER = @HAVE_UID_WRAPPER@ +INI_CONFIG_CFLAGS = @INI_CONFIG_CFLAGS@ +INI_CONFIG_LIBS = @INI_CONFIG_LIBS@ +INI_CONFIG_V0_CFLAGS = @INI_CONFIG_V0_CFLAGS@ +INI_CONFIG_V0_LIBS = @INI_CONFIG_V0_LIBS@ +INI_CONFIG_V1_1_CFLAGS = @INI_CONFIG_V1_1_CFLAGS@ +INI_CONFIG_V1_1_LIBS = @INI_CONFIG_V1_1_LIBS@ +INI_CONFIG_V1_3_CFLAGS = @INI_CONFIG_V1_3_CFLAGS@ +INI_CONFIG_V1_3_LIBS = @INI_CONFIG_V1_3_LIBS@ +INI_CONFIG_V1_CFLAGS = @INI_CONFIG_V1_CFLAGS@ +INI_CONFIG_V1_LIBS = @INI_CONFIG_V1_LIBS@ +INOTIFY_LIBS = @INOTIFY_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +JANSSON_CFLAGS = @JANSSON_CFLAGS@ +JANSSON_LIBS = @JANSSON_LIBS@ +JOSE_CFLAGS = @JOSE_CFLAGS@ +JOSE_LIBS = @JOSE_LIBS@ +JOURNALD_CFLAGS = @JOURNALD_CFLAGS@ +JOURNALD_LIBS = @JOURNALD_LIBS@ +KEYUTILS_LIBS = @KEYUTILS_LIBS@ +KRAD_LIBS = @KRAD_LIBS@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_CONFIG = @KRB5_CONFIG@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDB_CFLAGS = @LDB_CFLAGS@ +LDB_LIBS = @LDB_LIBS@ +LDFLAGS = @LDFLAGS@ +LIBADD_DL = @LIBADD_DL@ +LIBADD_DLD_LINK = @LIBADD_DLD_LINK@ +LIBADD_DLOPEN = @LIBADD_DLOPEN@ +LIBADD_SHL_LOAD = @LIBADD_SHL_LOAD@ +LIBADD_TIMER = @LIBADD_TIMER@ +LIBCLOCK_GETTIME = @LIBCLOCK_GETTIME@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBNL1_CFLAGS = @LIBNL1_CFLAGS@ +LIBNL1_LIBS = @LIBNL1_LIBS@ +LIBNL3_CFLAGS = @LIBNL3_CFLAGS@ +LIBNL3_LIBS = @LIBNL3_LIBS@ +LIBNL_CFLAGS = @LIBNL_CFLAGS@ +LIBNL_LIBS = @LIBNL_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +LT_DLLOADERS = @LT_DLLOADERS@ +LT_DLPREOPEN = @LT_DLPREOPEN@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MKINSTALLDIRS = @MKINSTALLDIRS@ +MSGFMT = @MSGFMT@ +MSGMERGE = @MSGMERGE@ +NDR_KRB5PAC_CFLAGS = @NDR_KRB5PAC_CFLAGS@ +NDR_KRB5PAC_LIBS = @NDR_KRB5PAC_LIBS@ +NDR_NBT_CFLAGS = @NDR_NBT_CFLAGS@ +NDR_NBT_LIBS = @NDR_NBT_LIBS@ +NFSIDMAP_CFLAGS = @NFSIDMAP_CFLAGS@ +NFSIDMAP_LIBS = @NFSIDMAP_LIBS@ +NFSIDMAP_OBJ = @NFSIDMAP_OBJ@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NSUPDATE = @NSUPDATE@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENLDAP_CFLAGS = @OPENLDAP_CFLAGS@ +OPENLDAP_LIBS = @OPENLDAP_LIBS@ +OPENSSL = @OPENSSL@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +P11TOOL = @P11TOOL@ +P11_KIT_CFLAGS = @P11_KIT_CFLAGS@ +P11_KIT_LIBS = @P11_KIT_LIBS@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PAM_LIBS = @PAM_LIBS@ +PAM_MISC_LIBS = @PAM_MISC_LIBS@ +PASSKEY_CFLAGS = @PASSKEY_CFLAGS@ +PASSKEY_LIBS = @PASSKEY_LIBS@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PCRE_CFLAGS = @PCRE_CFLAGS@ +PCRE_LIBS = @PCRE_LIBS@ +PKG_CONFIG = @PKG_CONFIG@ +PO4A = @PO4A@ +POPT_CFLAGS = @POPT_CFLAGS@ +POPT_LIBS = @POPT_LIBS@ +POSUB = @POSUB@ +PRERELEASE_VERSION = @PRERELEASE_VERSION@ +PYTHON = @PYTHON@ +PYTHON2 = @PYTHON2@ +PYTHON2_CFLAGS = @PYTHON2_CFLAGS@ +PYTHON2_EXEC_PREFIX = @PYTHON2_EXEC_PREFIX@ +PYTHON2_INCLUDES = @PYTHON2_INCLUDES@ +PYTHON2_LIBS = @PYTHON2_LIBS@ +PYTHON2_PREFIX = @PYTHON2_PREFIX@ +PYTHON2_VERSION = @PYTHON2_VERSION@ +PYTHON3 = @PYTHON3@ +PYTHON3_CFLAGS = @PYTHON3_CFLAGS@ +PYTHON3_EXEC_PREFIX = @PYTHON3_EXEC_PREFIX@ +PYTHON3_INCLUDES = @PYTHON3_INCLUDES@ +PYTHON3_LIBS = @PYTHON3_LIBS@ +PYTHON3_PREFIX = @PYTHON3_PREFIX@ +PYTHON3_VERSION = @PYTHON3_VERSION@ +PYTHON_CONFIG = @PYTHON_CONFIG@ +PYTHON_EXEC = @PYTHON_EXEC@ +PYTHON_EXEC_INTG = @PYTHON_EXEC_INTG@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +RESOLV_CFLAGS = @RESOLV_CFLAGS@ +RESOLV_LIBS = @RESOLV_LIBS@ +SAMBA_UTIL_CFLAGS = @SAMBA_UTIL_CFLAGS@ +SAMBA_UTIL_LIBS = @SAMBA_UTIL_LIBS@ +SASL_CFLAGS = @SASL_CFLAGS@ +SASL_LIBS = @SASL_LIBS@ +SED = @SED@ +SELINUX_LIBS = @SELINUX_LIBS@ +SEMANAGE_LIBS = @SEMANAGE_LIBS@ +SERVICE = @SERVICE@ +SET_MAKE = @SET_MAKE@ +SGML_CATALOG_FILES = @SGML_CATALOG_FILES@ +SHELL = @SHELL@ +SLAPD = @SLAPD@ +SMBCLIENT_CFLAGS = @SMBCLIENT_CFLAGS@ +SMBCLIENT_LIBS = @SMBCLIENT_LIBS@ +SOFTHSM2_PATH = @SOFTHSM2_PATH@ +SOFTHSM2_UTIL = @SOFTHSM2_UTIL@ +SSH_KEYGEN = @SSH_KEYGEN@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +SSSD_USER = @SSSD_USER@ +STRIP = @STRIP@ +SYSTEMD_DAEMON_CFLAGS = @SYSTEMD_DAEMON_CFLAGS@ +SYSTEMD_DAEMON_LIBS = @SYSTEMD_DAEMON_LIBS@ +SYSTEMD_LOGIN_CFLAGS = @SYSTEMD_LOGIN_CFLAGS@ +SYSTEMD_LOGIN_LIBS = @SYSTEMD_LOGIN_LIBS@ +TALLOC_CFLAGS = @TALLOC_CFLAGS@ +TALLOC_LIBS = @TALLOC_LIBS@ +TDB_CFLAGS = @TDB_CFLAGS@ +TDB_LIBS = @TDB_LIBS@ +TEST_DIR = @TEST_DIR@ +TEVENT_CFLAGS = @TEVENT_CFLAGS@ +TEVENT_LIBS = @TEVENT_LIBS@ +UNICODE_LIBS = @UNICODE_LIBS@ +USE_NLS = @USE_NLS@ +UUID_CFLAGS = @UUID_CFLAGS@ +UUID_LIBS = @UUID_LIBS@ +VALGRIND = @VALGRIND@ +VALGRIND_ENABLED = @VALGRIND_ENABLED@ +VERSION = @VERSION@ +XGETTEXT = @XGETTEXT@ +XMLLINT = @XMLLINT@ +XSLTPROC = @XSLTPROC@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +appmodpath = @appmodpath@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +cifspluginpath = @cifspluginpath@ +config_def_ccache_dir = @config_def_ccache_dir@ +config_def_ccname_template = @config_def_ccname_template@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dbpath = @dbpath@ +docdir = @docdir@ +dvidir = @dvidir@ +environment_file = @environment_file@ +exec_prefix = @exec_prefix@ +gpocachepath = @gpocachepath@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +initdir = @initdir@ +install_sh = @install_sh@ +krb5authdatapluginpath = @krb5authdatapluginpath@ +krb5pluginpath = @krb5pluginpath@ +krb5rcachedir = @krb5rcachedir@ +ldblibdir = @ldblibdir@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +logpath = @logpath@ +mandir = @mandir@ +mcpath = @mcpath@ +mkdir_p = @mkdir_p@ +nfsidmaplibdir = @nfsidmaplibdir@ +nfslibpath = @nfslibpath@ +nsslibdir = @nsslibdir@ +oldincludedir = @oldincludedir@ +pammoddir = @pammoddir@ +pdfdir = @pdfdir@ +pidpath = @pidpath@ +pipepath = @pipepath@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = $(python3dir)/sssd +pluginpath = @pluginpath@ +polkitdir = @polkitdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pubconfpath = @pubconfpath@ +py2execdir = @py2execdir@ +py3execdir = @py3execdir@ +pyexecdir = @pyexecdir@ +python2dir = @python2dir@ +python3dir = @python3dir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +secdbpath = @secdbpath@ +session_recording_shell = @session_recording_shell@ +sharedbuilddir = @sharedbuilddir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +subidlibpath = @subidlibpath@ +sudolibpath = @sudolibpath@ +sysconfdir = @sysconfdir@ +systemdconfdir = @systemdconfdir@ +systemdunitdir = @systemdunitdir@ +tapset_dir = @tapset_dir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +valgrind_enabled_tools = @valgrind_enabled_tools@ +valgrind_tools = @valgrind_tools@ +winbindpluginpath = @winbindpluginpath@ +sss_analyze_pythondir = $(libexecdir)/sssd +dist_sss_analyze_python_SCRIPTS = \ + sss_analyze \ + $(NULL) + +dist_pkgpython_DATA = \ + __init__.py \ + source_files.py \ + source_journald.py \ + source_reader.py \ + parser.py \ + sss_analyze.py \ + $(NULL) + +modulesdir = $(pkgpythondir)/modules +dist_modules_DATA = \ + modules/__init__.py \ + modules/request.py \ + $(NULL) + +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/tools/analyzer/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/tools/analyzer/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-dist_sss_analyze_pythonSCRIPTS: $(dist_sss_analyze_python_SCRIPTS) + @$(NORMAL_INSTALL) + @list='$(dist_sss_analyze_python_SCRIPTS)'; test -n "$(sss_analyze_pythondir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(sss_analyze_pythondir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(sss_analyze_pythondir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n' \ + -e 'h;s|.*|.|' \ + -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) { files[d] = files[d] " " $$1; \ + if (++n[d] == $(am__install_max)) { \ + print "f", d, files[d]; n[d] = 0; files[d] = "" } } \ + else { print "f", d "/" $$4, $$1 } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(sss_analyze_pythondir)$$dir'"; \ + $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(sss_analyze_pythondir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-dist_sss_analyze_pythonSCRIPTS: + @$(NORMAL_UNINSTALL) + @list='$(dist_sss_analyze_python_SCRIPTS)'; test -n "$(sss_analyze_pythondir)" || exit 0; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 's,.*/,,;$(transform)'`; \ + dir='$(DESTDIR)$(sss_analyze_pythondir)'; $(am__uninstall_files_from_dir) + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-dist_modulesDATA: $(dist_modules_DATA) + @$(NORMAL_INSTALL) + @list='$(dist_modules_DATA)'; test -n "$(modulesdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(modulesdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(modulesdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(modulesdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(modulesdir)" || exit $$?; \ + done + +uninstall-dist_modulesDATA: + @$(NORMAL_UNINSTALL) + @list='$(dist_modules_DATA)'; test -n "$(modulesdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(modulesdir)'; $(am__uninstall_files_from_dir) +install-dist_pkgpythonDATA: $(dist_pkgpython_DATA) + @$(NORMAL_INSTALL) + @list='$(dist_pkgpython_DATA)'; test -n "$(pkgpythondir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkgpythondir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkgpythondir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkgpythondir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgpythondir)" || exit $$?; \ + done + +uninstall-dist_pkgpythonDATA: + @$(NORMAL_UNINSTALL) + @list='$(dist_pkgpython_DATA)'; test -n "$(pkgpythondir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkgpythondir)'; $(am__uninstall_files_from_dir) +tags TAGS: + +ctags CTAGS: + +cscope cscopelist: + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(SCRIPTS) $(DATA) +installdirs: + for dir in "$(DESTDIR)$(sss_analyze_pythondir)" "$(DESTDIR)$(modulesdir)" "$(DESTDIR)$(pkgpythondir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-dist_modulesDATA install-dist_pkgpythonDATA \ + install-dist_sss_analyze_pythonSCRIPTS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-dist_modulesDATA uninstall-dist_pkgpythonDATA \ + uninstall-dist_sss_analyze_pythonSCRIPTS + +.MAKE: install-am install-strip + +.PHONY: all all-am check check-am clean clean-generic clean-libtool \ + cscopelist-am ctags-am distclean distclean-generic \ + distclean-libtool distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am \ + install-dist_modulesDATA install-dist_pkgpythonDATA \ + install-dist_sss_analyze_pythonSCRIPTS install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags-am uninstall uninstall-am uninstall-dist_modulesDATA \ + uninstall-dist_pkgpythonDATA \ + uninstall-dist_sss_analyze_pythonSCRIPTS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/tools/analyzer/__init__.py b/src/tools/analyzer/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/tools/analyzer/__init__.py diff --git a/src/tools/analyzer/modules/__init__.py b/src/tools/analyzer/modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/tools/analyzer/modules/__init__.py diff --git a/src/tools/analyzer/modules/request.py b/src/tools/analyzer/modules/request.py new file mode 100644 index 0000000..d661ddd --- /dev/null +++ b/src/tools/analyzer/modules/request.py @@ -0,0 +1,320 @@ +import re +import logging + +from sssd.parser import SubparsersAction +from sssd.parser import Option + +logger = logging.getLogger() + + +class RequestAnalyzer: + """ + A request analyzer module, handles request tracking logic + and analysis. Parses input generated from a source Reader. + """ + module_parser = None + consumed_logs = [] + list_opts = [ + Option('--verbose', 'Verbose output', bool, '-v'), + Option('--pam', 'Filter only PAM requests', bool), + ] + + show_opts = [ + Option('cid', 'Track request with this ID', int), + Option('--child', 'Include child process logs', bool), + Option('--merge', 'Merge logs together sorted by timestamp', bool), + Option('--pam', 'Track only PAM requests', bool), + ] + + def print_module_help(self, args): + """ + Print the module parser help output + + Args: + args (Namespace): argparse parsed arguments + """ + self.module_parser.print_help() + + def setup_args(self, parser_grp, cli): + """ + Setup module parser, subcommands, and options + + Args: + parser_grp (argparse.Action): Parser group to nest + module and subcommands under + """ + desc = "Analyze request tracking module" + self.module_parser = parser_grp.add_parser('request', + description=desc, + help='Request tracking') + + subparser = self.module_parser.add_subparsers(title=None, + dest='subparser', + action=SubparsersAction, + metavar='COMMANDS') + + subcmd_grp = subparser.add_parser_group('Operation Modes') + cli.add_subcommand(subcmd_grp, 'list', 'List recent requests', + self.list_requests, self.list_opts) + cli.add_subcommand(subcmd_grp, 'show', 'Track individual request ID', + self.track_request, self.show_opts) + + self.module_parser.set_defaults(func=self.print_module_help) + + return self.module_parser + + def load(self, args): + """ + Load the appropriate source reader. + + Args: + args (Namespace): argparse parsed arguments + + Returns: + Instantiated source object + """ + if args.source == "journald": + from sssd.source_journald import Journald + source = Journald() + else: + from sssd.source_files import Files + source = Files(args.logdir) + return source + + def matched_line(self, source, patterns): + """ + Yield lines which match any number of patterns (OR) in + provided patterns list. + + Args: + source (Reader): source Reader object + Yields: + lines matching the provided pattern(s) + """ + for line in source: + for pattern in patterns: + re_obj = re.compile(pattern) + if re_obj.search(line): + if line.startswith(' * '): + continue + yield line + + def get_linked_ids(self, source, pattern, regex): + """ + Retrieve list of associated REQ_TRACE ids. Filter + only source lines by pattern, then parse out the + linked id with the provided regex. + + Args: + source (Reader): source Reader object + pattern (list of str): regex pattern(s) used for finding + linked ids + regex (str): regular expression used to extract linked id + + Returns: + List of linked ids discovered + """ + linked_ids = [] + for match in self.matched_line(source, pattern): + id_re = re.compile(regex) + match = id_re.search(match) + if match: + found = match.group(0) + linked_ids.append(found) + return linked_ids + + def consume_line(self, line, source, consume): + """ + Print or consume a line, if merge cli option is provided then consume + boolean is set to True + + Args: + line (str): line to process + source (Reader): source Reader object + consume (bool): If True, line is added to consume_logs + list, otherwise print line + + Returns: + True if line was processed, otherwise False + """ + found_results = True + if consume: + self.consumed_logs.append(line.rstrip(line[-1])) + else: + # files source includes newline + if type(source).__name__ == 'Files': + print(line, end='') + else: + print(line) + return found_results + + def print_formatted_verbose(self, source): + """ + Parse log file and print formatted verbose list_requests output + + Args: + source (Reader): source Reader object + """ + data = {} + # collect cid log lines from single run through of parsing the log + # into dictionary # (cid, ts) -> logline_output + for line in source: + if "CID#" not in line: + continue + + # parse CID and ts from line, key is a tuple of (cid,ts) + fields = line.split("[") + # timestamp to the minute, cut off seconds, ms + ts = fields[0][:17] + result = re.search('CID#[0-9]*', fields[3]) + cid = result.group(0) + + # if mapping exists, append line to output. Otherwise create new mapping + if (cid, ts) in data.keys(): + data[(cid, ts)] += line + else: + data[(cid, ts)] = line + + # pretty print the data + for k, v in data.items(): + cr_done = [] + id_done = [] + for cidline in v.splitlines(): + plugin = "" + name = "" + id = "" + + # CR number + fields = cidline.split("[") + cr_field = fields[3][7:] + cr = cr_field.split(":")[0][4:] + # Client connected, top-level info line + if re.search(r'\[cmd', cidline): + self.print_formatted(cidline) + # CR Plugin name + if re.search("cache_req_send", cidline): + plugin = cidline.split('\'')[1] + id_done.clear() + # Extract CR number + fields = cidline.split("[") + cr_field = fields[3][7:] + cr = cr_field.split(":")[0][4:] + # CR Input name + elif re.search("cache_req_process_input", cidline): + name = cidline.rsplit('[')[-1] + # CR Input id + elif re.search("cache_req_search_send", cidline): + id = cidline.rsplit()[-1] + + if plugin: + print(" - " + plugin) + if name: + # Avoid duplicate output with the same CR # + if cr not in cr_done: + print(" - " + name[:-1]) + cr_done.append(cr) + if (id and ("UID" in cidline or "GID" in cidline)): + if id not in id_done and bool(re.search(r'\d', id)): + print(" - " + id) + id_done.append(id) + + def print_formatted(self, line): + """ + Parse line and print formatted list_requests output + + Args: + line (str): line to parse + Returns: + Client ID from printed line, 0 otherwise + """ + # exclude backtrace logs + if line.startswith(' * '): + return 0 + if "refreshed" in line: + return 0 + ts = line.split(")")[0] + ts = ts[1:] + fields = line.split("[") + cid = fields[3][4:-9] + cmd = fields[4][4:-1] + uid = fields[5][4:-1] + if not uid.isnumeric(): + uid = fields[6][4:-1] + print(f'{ts}: [uid {uid}] CID #{cid}: {cmd}') + return cid + + def list_requests(self, args): + """ + List component (default: NSS) responder requests + + Args: + args (Namespace): populated argparse namespace + """ + source = self.load(args) + component = source.Component.NSS + resp = "nss" + # Log messages matching the following regex patterns contain + # the useful info we need to produce list output + patterns = [r'\[cmd'] + if args.pam: + component = source.Component.PAM + resp = "pam" + + logger.info(f"******** Listing {resp} client requests ********") + source.set_component(component, False) + + if args.verbose: + self.print_formatted_verbose(source) + else: + for line in self.matched_line(source, patterns): + if type(source).__name__ == 'Journald': + print(line) + else: + self.print_formatted(line) + + def track_request(self, args): + """ + Print Logs pertaining to individual SSSD client request + + Args: + args (Namespace): populated argparse namespace + """ + source = self.load(args) + cid = args.cid + resp_results = False + be_results = False + component = source.Component.NSS + resp = "nss" + pattern = [rf"\[CID#{cid}\]"] + + if args.pam: + component = source.Component.PAM + resp = "pam" + + logger.info(f"******** Checking {resp} responder for Client ID" + f" {cid} *******") + source.set_component(component, args.child) + for match in self.matched_line(source, pattern): + resp_results = self.consume_line(match, source, args.merge) + + logger.info(f"********* Checking Backend for Client ID {cid} ********") + pattern = [rf'REQ_TRACE.*\[sssd.{resp} CID #{cid}\]'] + source.set_component(source.Component.BE, args.child) + + be_id_regex = r'\[RID#[0-9]+\]' + be_ids = self.get_linked_ids(source, pattern, be_id_regex) + + pattern.clear() + [pattern.append(f'\\{id}') for id in be_ids] + + for match in self.matched_line(source, pattern): + be_results = self.consume_line(match, source, args.merge) + + if args.merge: + # sort by date/timestamp + sorted_list = sorted(self.consumed_logs, + key=lambda s: s.split(')')[0]) + for entry in sorted_list: + print(entry) + if not resp_results and not be_results: + logger.warn(f"ID {cid} not found in logs!") diff --git a/src/tools/analyzer/parser.py b/src/tools/analyzer/parser.py new file mode 100644 index 0000000..742694e --- /dev/null +++ b/src/tools/analyzer/parser.py @@ -0,0 +1,60 @@ +import argparse + + +# Based on patch from https://bugs.python.org/issue9341 +class SubparsersAction(argparse._SubParsersAction): + """ + Provide a subparser action that can create subparsers with ability of + grouping arguments. + + It is based on the patch from: + + - https://bugs.python.org/issue9341 + """ + + class _PseudoGroup(argparse.Action): + def __init__(self, container, title): + super().__init__(option_strings=[], dest=title) + self.container = container + self._choices_actions = [] + + def add_parser(self, name, **kwargs): + # add the parser to the main Action, but move the pseudo action + # in the group's own list + parser = self.container.add_parser(name, **kwargs) + choice_action = self.container._choices_actions.pop() + self._choices_actions.append(choice_action) + return parser + + def _get_subactions(self): + return self._choices_actions + + def add_parser_group(self, title): + # the formatter can handle recursive subgroups + grp = SubparsersAction._PseudoGroup(self, title) + self._choices_actions.append(grp) + return grp + + def add_parser_group(self, title): + """ + Add new parser group. + + :param title: Title. + :type title: str + :return: Parser group that can have additional parsers attached. + :rtype: ``argparse.Action`` extended with ``add_parser`` method + """ + grp = self._PseudoGroup(self, title) + self._choices_actions.append(grp) + return grp + + +class Option: + """ + Group option attributes for command/subcommand options + """ + def __init__(self, name, help_msg, opt_type, short_opt=None): + self.name = name + self.short_opt = short_opt + self.help_msg = help_msg + self.opt_type = opt_type diff --git a/src/tools/analyzer/source_files.py b/src/tools/analyzer/source_files.py new file mode 100644 index 0000000..0cadf99 --- /dev/null +++ b/src/tools/analyzer/source_files.py @@ -0,0 +1,76 @@ +import glob +import logging + +from sssd.source_reader import Reader + +logger = logging.getLogger() + + +class Files(Reader): + """ + A class used to represent a Log Files Reader + + Args: + path -- the path where SSSD logs are to + be read (default /var/log/sssd/) + """ + + def __init__(self, path): + super().__init__() + self.log_files = [] + self.path = self.resolve_path(path) + self.domains = self.get_domain_logfiles() + + def __iter__(self): + """ + Yields: + str: The next line in the log file + """ + for files in self.log_files: + try: + with open(files) as file: + for line in file: + yield line + except FileNotFoundError as err: + logger.warning("Could not find domain log file, skipping") + logger.warning(err) + continue + + def resolve_path(self, path): + if path.endswith("/"): + return path + else: + return path + "/" + + def get_domain_logfiles(self, child=False): + """ Retrieve list of SSSD log files, exclude rotated (.gz) files """ + domain_files = [] + exclude_list = ["ifp", "nss", "pam", "sudo", "autofs", + "ssh", "pac", "kcm", ".gz"] + if child: + file_list = glob.glob(self.path + "*.log") + else: + file_list = glob.glob(self.path + "sssd_*") + for file in file_list: + if not any(s in file for s in exclude_list): + domain_files.append(file) + + return domain_files + + def set_component(self, component, child): + """ + Switch the reader to interact with a certain SSSD component + NSS, PAM, BE + """ + self.log_files = [] + if component == self.Component.NSS: + self.log_files.append(self.path + "sssd_nss.log") + elif component == self.Component.PAM: + self.log_files.append(self.path + "sssd_pam.log") + elif component == self.Component.BE: + domains = self.get_domain_logfiles(child) + if not domains: + raise IOError + # error: No domains found? + for dom in domains: + self.log_files.append(dom) diff --git a/src/tools/analyzer/source_journald.py b/src/tools/analyzer/source_journald.py new file mode 100644 index 0000000..22ec2ed --- /dev/null +++ b/src/tools/analyzer/source_journald.py @@ -0,0 +1,46 @@ +from systemd import journal + +from sssd.source_reader import Reader + +_EXE_PREFIX = "/usr/libexec/sssd/" +_NSS_MATCH = _EXE_PREFIX + "sssd_nss" +_PAM_MATCH = _EXE_PREFIX + "sssd_pam" +_BE_MATCH = _EXE_PREFIX + "sssd_be" + + +class Journald(Reader): + """ + A class used to represent a Journald Reader + """ + def __init__(self): + super().__init__() + self.reader = journal.Reader() + self.reader.this_boot() + self.reader.seek_head() + + def __iter__(self): + """ + Yields: + str: The next journal entry message, with timestamp if found + """ + self.reader.seek_head() + for entry in self.reader: + ts = entry['__REALTIME_TIMESTAMP'] + msg = entry['MESSAGE'] + if ts: + yield f'{ts}: {msg}' + else: + yield msg + + def set_component(self, component, child): + """ + Switch the reader to interact with a certain SSSD component + NSS, PAM, BE + """ + self.reader.flush_matches() + if component == self.Component.NSS: + self.reader.add_match(_EXE=_NSS_MATCH) + elif component == self.Component.PAM: + self.reader.add_match(_EXE=_PAM_MATCH) + elif component == self.Component.BE: + self.reader.add_match(_EXE=_BE_MATCH) diff --git a/src/tools/analyzer/source_reader.py b/src/tools/analyzer/source_reader.py new file mode 100644 index 0000000..10d2d37 --- /dev/null +++ b/src/tools/analyzer/source_reader.py @@ -0,0 +1,27 @@ +from enum import Enum + +from abc import ABC, abstractmethod + + +class Reader(ABC): + """ + An abstract class used to represent a source Reader + """ + + class Component(Enum): + """ SSSD component to enable for reading """ + NSS = 1 # NSS Responder + PAM = 2 # PAM Responder + BE = 3 # Backend + + @abstractmethod + def __init__(self): + pass + + @abstractmethod + def __iter__(self): + pass + + @abstractmethod + def set_component(self): + pass diff --git a/src/tools/analyzer/sss_analyze b/src/tools/analyzer/sss_analyze new file mode 100755 index 0000000..3f1beaf --- /dev/null +++ b/src/tools/analyzer/sss_analyze @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +from sssd import sss_analyze + +sss_analyze.run() diff --git a/src/tools/analyzer/sss_analyze.py b/src/tools/analyzer/sss_analyze.py new file mode 100644 index 0000000..18b998f --- /dev/null +++ b/src/tools/analyzer/sss_analyze.py @@ -0,0 +1,104 @@ +import argparse + +from sssd.modules import request +from sssd.parser import SubparsersAction + + +class Analyzer: + def add_subcommand(self, subcmd_grp, name, help_msg, func, opts): + """ + Add subcommand to existing subcommand group + + Args: + name(str): Subcommand name + help_msg(str): Help message for subcommand + func(function): Function to call on execution + opts(list of Object()): List of Option objects to add to subcommand + """ + # Create parser + req_parser = subcmd_grp.add_parser(name, help=help_msg) + + # Add subcommand options + self._add_subcommand_options(req_parser, opts) + + # Execute func() when argument is called + req_parser.set_defaults(func=func) + + def _add_subcommand_options(self, parser, opts): + """ + Add subcommand options to subcommand parser + + Args: + parser(str): Subcommand group parser + opts(list of Object()): List of Option objects to add to subcommand + """ + for opt in opts: + if opt.opt_type is bool: + if opt.short_opt is None: + parser.add_argument(opt.name, help=opt.help_msg, + action='store_true') + else: + parser.add_argument(opt.name, opt.short_opt, + help=opt.help_msg, action='store_true') + if opt.opt_type is int: + parser.add_argument(opt.name, help=opt.help_msg, + type=int) + + def load_modules(self, parser, parser_grp): + """ + Initialize analyzer modules from modules/* + + Args: + parser (ArgumentParser): Base parser object + parser_grp (argparse.Action): Parser group that can have + additional parsers attached. + """ + # Currently only the 'request' module exists + req = request.RequestAnalyzer() + cli = Analyzer() + + req.setup_args(parser_grp, cli) + + def setup_args(self): + """ + Top-level argument setup function. + Setup analyzer argument parsers and subcommand parser/options. + + Returns: + parser (ArgumentParser): Base parser object + """ + # top level parser + formatter = argparse.RawTextHelpFormatter + parser = argparse.ArgumentParser(description='Analyzer tool to assist ' + 'with SSSD log parsing', + formatter_class=formatter) + parser.add_argument('--source', default='files', choices=['files', + 'journald']) + parser.add_argument('--logdir', default='/var/log/sssd/', + help='SSSD Log directory to parse log files from') + + # Modules parser group + subparser = parser.add_subparsers(title=None, + action=SubparsersAction, + metavar='COMMANDS') + parser_grp = subparser.add_parser_group('Modules') + + # Load modules, subcommands are added in module.setup_args() + self.load_modules(parser, parser_grp) + + return parser + + def main(self): + parser = self.setup_args() + args = parser.parse_args() + + if not hasattr(args, 'func'): + parser.print_help() + return 0 + + args.func(args) + + +def run(): + analyzer = Analyzer() + analyzer.main() diff --git a/src/tools/common/sss_colondb.c b/src/tools/common/sss_colondb.c new file mode 100644 index 0000000..41e6c3a --- /dev/null +++ b/src/tools/common/sss_colondb.c @@ -0,0 +1,317 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdlib.h> + +#include "util/util.h" +#include "util/strtonum.h" +#include "tools/common/sss_colondb.h" + +#define IS_STD_FILE(db) ((db)->file == stdin || (db)->file == stdout) + +static char *read_field_as_string(char *line, + const char **_value) +{ + char *rest; + char *value; + + if (line == NULL || *line == '\n' || *line == '\0') { + /* There is nothing else to read. */ + rest = NULL; + value = NULL; + goto done; + } + + if (*line == ':') { + /* Special case for empty value. */ + *line = '\0'; + rest = line + 1; + value = NULL; + goto done; + } + + /* Value starts at current position. */ + value = line; + + /* Find next field delimiter. */ + rest = strchr(line, ':'); + if (rest == NULL) { + /* There is no more field. Remove \n from the end. */ + rest = strchr(line, '\n'); + if (rest != NULL) { + *rest = '\0'; + rest = NULL; + } + goto done; + } + + /* Remove it and step one character further. */ + *rest = '\0'; + rest++; + +done: + *_value = value; + + return rest; +} + +static char *read_field_as_uint32(char *line, + uint32_t *_value) +{ + const char *str; + char *rest; + errno_t ret; + char *endptr; + + rest = read_field_as_string(line, &str); + if (str == NULL) { + *_value = 0; + return rest; + } + + *_value = strtouint32(str, &endptr, 10); + if ((errno != 0) || *endptr || (str == endptr)) { + ret = errno ? errno : EINVAL; + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse number [%d]: %s\n", + ret, sss_strerror(ret)); + + *_value = 0; + } + + return rest; +} + +struct sss_colondb { + FILE *file; + enum sss_colondb_mode mode; +}; + +errno_t sss_colondb_readline(TALLOC_CTX *mem_ctx, + struct sss_colondb *db, + struct sss_colondb_read_field *table) +{ + int readchars; + size_t linelen = 0; + char *line = NULL; + char *tcline; + char *rest; + errno_t ret; + int i; + + if (db->mode != SSS_COLONDB_READ) { + return ERR_INTERNAL; + } + + readchars = getline(&line, &linelen, db->file); + if (readchars == -1) { + /* Nothing was read. */ + + free(line); + line = NULL; + + if (errno != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to read line [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + return EOF; + } + + /* Copy line to mem_ctx. */ + tcline = talloc_strdup(mem_ctx, line); + + free(line); + line = NULL; + + if (tcline == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + return ENOMEM; + } + + rest = tcline; + for (i = 0; table[i].type != SSS_COLONDB_SENTINEL; i++) { + switch (table[i].type) { + case SSS_COLONDB_UINT32: + rest = read_field_as_uint32(rest, table[i].data.uint32); + break; + case SSS_COLONDB_STRING: + rest = read_field_as_string(rest, table[i].data.str); + break; + case SSS_COLONDB_SENTINEL: + DEBUG(SSSDBG_CRIT_FAILURE, "Trying to process sentinel?!\n"); + ret = ERR_INTERNAL; + goto done; + } + + if (rest == NULL && table[i + 1].type != SSS_COLONDB_SENTINEL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Line contains less values than expected!\n"); + ret = EINVAL; + goto done; + } else if (rest != NULL && table[i + 1].type == SSS_COLONDB_SENTINEL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Line contains more values than expected!\n"); + ret = EINVAL; + goto done; + } + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(tcline); + } + + return ret; +} + +errno_t sss_colondb_writeline(struct sss_colondb *db, + struct sss_colondb_write_field *table) +{ + TALLOC_CTX *tmp_ctx; + char *line = NULL; + errno_t ret; + int i; + + if (db->mode != SSS_COLONDB_WRITE) { + return ERR_INTERNAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + line = talloc_strdup(tmp_ctx, ""); + if (line == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed.\n"); + ret = ENOMEM; + goto done; + } + + for (i = 0; table[i].type != SSS_COLONDB_SENTINEL; i++) { + switch (table[i].type) { + case SSS_COLONDB_UINT32: + if (table[i].data.uint32 == 0) { + line = talloc_asprintf_append(line, ":"); + } else { + line = talloc_asprintf_append(line, ":%u", table[i].data.uint32); + } + break; + case SSS_COLONDB_STRING: + if (table[i].data.str == NULL) { + line = talloc_asprintf_append(line, ":"); + } else { + line = talloc_asprintf_append(line, ":%s", table[i].data.str); + } + break; + case SSS_COLONDB_SENTINEL: + DEBUG(SSSDBG_CRIT_FAILURE, "Trying to process sentinel?!\n"); + ret = ERR_INTERNAL; + goto done; + } + + if (line == NULL) { + ret = ENOMEM; + goto done; + } + } + + /* Remove starting : */ + line++; + + fprintf(db->file, "%s\n", line); + fflush(db->file); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static int sss_colondb_close(void *pvt) +{ + struct sss_colondb *db = talloc_get_type(pvt, struct sss_colondb); + + if (db->file == NULL || IS_STD_FILE(db)) { + return 0; + } + + fclose(db->file); + db->file = NULL; + + return 0; +} + +static FILE *open_db(const char *filename, enum sss_colondb_mode mode) +{ + FILE *fp = NULL; + errno_t ret; + + errno = 0; + + switch (mode) { + case SSS_COLONDB_READ: + fp = filename == NULL ? stdin : fopen(filename, "r"); + break; + case SSS_COLONDB_WRITE: + fp = filename == NULL ? stdout : fopen(filename, "w"); + break; + } + + if (fp == NULL && filename != NULL) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to open file %s [%d]: %s\n", + filename, ret, sss_strerror(ret)); + } + + return fp; +} + +struct sss_colondb *sss_colondb_open(TALLOC_CTX *mem_ctx, + enum sss_colondb_mode mode, + const char *filename) +{ + struct sss_colondb *db; + + db = talloc_zero(mem_ctx, struct sss_colondb); + if (db == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed\n"); + return NULL; + } + + db->file = open_db(filename, mode); + db->mode = mode; + + if (db->file == NULL) { + talloc_free(db); + return NULL; + } + + talloc_set_destructor((TALLOC_CTX *)db, sss_colondb_close); + + return db; +} diff --git a/src/tools/common/sss_colondb.h b/src/tools/common/sss_colondb.h new file mode 100644 index 0000000..cb90400 --- /dev/null +++ b/src/tools/common/sss_colondb.h @@ -0,0 +1,96 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SSS_COLONDB_H_ +#define _SSS_COLONDB_H_ + +#include <stdlib.h> +#include <stdint.h> +#include <errno.h> +#include <talloc.h> + +struct sss_colondb; + +enum sss_colondb_mode { + SSS_COLONDB_READ, + SSS_COLONDB_WRITE +}; + +enum sss_colondb_type { + SSS_COLONDB_UINT32, + SSS_COLONDB_STRING, + SSS_COLONDB_SENTINEL +}; + +union sss_colondb_write_data { + uint32_t uint32; + const char *str; +}; + +union sss_colondb_read_data { + uint32_t *uint32; + const char **str; +}; + +struct sss_colondb_write_field { + enum sss_colondb_type type; + union sss_colondb_write_data data; +}; + +struct sss_colondb_read_field { + enum sss_colondb_type type; + union sss_colondb_read_data data; +}; + +/** + * Open colon DB and return connection. + * @param[in|out] mem_ctx Memory context. Internal sss_colondb_close() is set + * on destructor of this memory context. + * @param[in] mode Open mode of db: SSS_COLONDB_READ or SSS_COLONDB_WRITE. + * @param[in] filename Name of file. + * @return Pointer to structure holding DB connection, or NULL if fail. + */ +struct sss_colondb *sss_colondb_open(TALLOC_CTX *mem_ctx, + enum sss_colondb_mode mode, + const char *filename); + +/** + * Read line from colon DB. + * @param[in|out] mem_ctx Memory context. + * @param[in] db Pointer to structure holding DB connection. + * @param[in|out] table Array of expected structure of line. It is expected + * that last item has SSS_COLONDB_SENTINEL type. + * @return EOK if success, else error code. + */ +errno_t sss_colondb_readline(TALLOC_CTX *mem_ctx, + struct sss_colondb *db, + struct sss_colondb_read_field *table); + +/** + * Write line to colon DB. + * @param[in] db Pointer to structure holding DB connection. + * @param[in] table Array with data. It is expected that last item has + * SSS_COLONDB_SENTINEL type. + * @return EOK if success, else error code. + */ +errno_t sss_colondb_writeline(struct sss_colondb *db, + struct sss_colondb_write_field *table); + +#endif /* _SSS_COLONDB_H_ */ diff --git a/src/tools/common/sss_process.c b/src/tools/common/sss_process.c new file mode 100644 index 0000000..fc710a5 --- /dev/null +++ b/src/tools/common/sss_process.c @@ -0,0 +1,124 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> + +#include "util/util.h" +#include "tools/common/sss_process.h" + +static pid_t parse_pid(const char *strpid) +{ + long value; + char *endptr; + + errno = 0; + value = strtol(strpid, &endptr, 10); + if ((errno != 0) || (endptr == strpid) + || ((*endptr != '\0') && (*endptr != '\n'))) { + return 0; + } + + return value; +} + +static errno_t sss_pid(pid_t *out_pid) +{ + int ret; + size_t fsize; + FILE *pid_file; + char pid_str[MAX_PID_LENGTH] = {'\0'}; + + *out_pid = 0; + + errno = 0; + pid_file = fopen(SSSD_PIDFILE, "r"); + if (pid_file == NULL) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to open pid file \"%s\": %s\n", + SSSD_PIDFILE, strerror(ret)); + goto done; + } + + fsize = fread(pid_str, sizeof(char), MAX_PID_LENGTH * sizeof(char), + pid_file); + if (!feof(pid_file)) { + /* eof not reached */ + ret = ferror(pid_file); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to read from file \"%s\": %s\n", + SSSD_PIDFILE, strerror(ret)); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "File \"%s\" contains invalid pid.\n", + SSSD_PIDFILE); + } + goto done; + } + if (fsize == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "File \"%s\" contains no pid.\n", + SSSD_PIDFILE); + ret = EINVAL; + goto done; + } + + pid_str[MAX_PID_LENGTH-1] = '\0'; + *out_pid = parse_pid(pid_str); + if (*out_pid == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "File \"%s\" contains invalid pid.\n", SSSD_PIDFILE); + ret = EINVAL; + goto done; + } + + ret = EOK; + +done: + if (pid_file != NULL) { + fclose(pid_file); + } + return ret; +} + +bool sss_daemon_running(void) +{ + return sss_signal(0) == EOK; +} + +errno_t sss_signal(int signum) +{ + int ret; + pid_t pid; + + ret = sss_pid(&pid); + if (ret != EOK) { + return ret; + } + + if (kill(pid, signum) != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not send signal %d to process %d: %s\n", + signum, pid, strerror(errno)); + return ret; + } + + return EOK; +} diff --git a/src/tools/common/sss_process.h b/src/tools/common/sss_process.h new file mode 100644 index 0000000..6bbb094 --- /dev/null +++ b/src/tools/common/sss_process.h @@ -0,0 +1,29 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SSS_PROCESS_H_ +#define _SSS_PROCESS_H_ + +#include "util/util.h" + +bool sss_daemon_running(void); +errno_t sss_signal(int signum); + +#endif /* _SSS_PROCESS_H_ */ diff --git a/src/tools/common/sss_tools.c b/src/tools/common/sss_tools.c new file mode 100644 index 0000000..e67de3a --- /dev/null +++ b/src/tools/common/sss_tools.c @@ -0,0 +1,617 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <talloc.h> +#include <stdlib.h> +#include <string.h> +#include <popt.h> + +#include "config.h" +#include "util/util.h" +#include "confdb/confdb.h" +#include "confdb/confdb_setup.h" +#include "db/sysdb.h" +#include "tools/common/sss_tools.h" + +static void sss_tool_print_common_opts(int min_len) +{ + ERROR("Help options:\n"); + fprintf(stderr, " %-*s\t %s\n", min_len, "-?, --help", + _("Show this for a command")); + fprintf(stderr, " %-*s\t %s\n", min_len, "--usage", + _("Show brief usage message for a command")); + ERROR("\n"); + + ERROR("Debug options:\n"); + fprintf(stderr, " %-*s\t %s\n", min_len, "--debug", + _("Enable debug log level of sssctl tool")); +} + +static struct poptOption *sss_tool_common_opts_table(void) +{ + static struct poptOption common_opts[] = { + {"debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, NULL, + 0, NULL, NULL }, + POPT_TABLEEND + }; + + common_opts[0].descrip = _("The debug level to run with"); + + return common_opts; +} + +static void sss_tool_common_opts(struct sss_tool_ctx *tool_ctx, + int *argc, const char **argv) +{ + poptContext pc; + int debug = SSSDBG_TOOLS_DEFAULT; + int orig_argc = *argc; + int help = 0; + + struct poptOption options[] = { + {"debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_STRIP, &debug, + 0, _("The debug level to run with"), NULL }, + {"help", '?', POPT_ARG_VAL | POPT_ARGFLAG_DOC_HIDDEN, &help, + 1, NULL, NULL }, + POPT_TABLEEND + }; + + pc = poptGetContext(argv[0], orig_argc, argv, options, 0); + while (poptGetNextOpt(pc) != -1) { + /* do nothing */ + } + + /* Strip common options from arguments. We will discard_const here, + * since it is not worth the trouble to convert it back and forth. */ + *argc = poptStrippedArgv(pc, orig_argc, discard_const_p(char *, argv)); + tool_ctx->print_help = help; + + DEBUG_CLI_INIT(debug); + + poptFreeContext(pc); +} + +static errno_t sss_tool_confdb_init(TALLOC_CTX *mem_ctx, + struct confdb_ctx **_confdb) +{ + struct confdb_ctx *confdb; + char *path; + errno_t ret; + + path = talloc_asprintf(mem_ctx, "%s/%s", DB_PATH, CONFDB_FILE); + if (path == NULL) { + return ENOMEM; + } + + ret = confdb_setup(mem_ctx, path, + SSSD_CONFIG_FILE, CONFDB_DEFAULT_CONFIG_DIR, + NULL, + &confdb); + talloc_zfree(path); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to setup ConfDB [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (_confdb != NULL) { + *_confdb = confdb; + } + + return EOK; +} + +static errno_t sss_tool_domains_init(TALLOC_CTX *mem_ctx, + struct confdb_ctx *confdb, + struct sss_domain_info **_domains) +{ + struct sss_domain_info *domains; + struct sss_domain_info *dom; + errno_t ret; + + ret = confdb_expand_app_domains(confdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to expand application domains [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = confdb_get_domains(confdb, &domains); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup domains [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = sysdb_init(mem_ctx, domains); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not initialize connection to the sysdb\n"); + return ret; + } + + for (dom = domains; dom != NULL; + dom = get_next_domain(dom, SSS_GND_DESCEND)) { + if (!IS_SUBDOMAIN(dom)) { + /* Get flat name and domain ID (SID) from the cache + * if available */ + ret = sysdb_master_domain_update(dom); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to update domain %s.\n", + dom->name); + } + + /* Update list of subdomains for this domain */ + ret = sysdb_update_subdomains(dom, confdb); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to update subdomains for domain %s.\n", + dom->name); + } + } + } + + for (dom = domains; dom != NULL; + dom = get_next_domain(dom, SSS_GND_DESCEND)) { + ret = sss_names_init(mem_ctx, confdb, dom->name, &dom->names); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_names_init() failed\n"); + return ret; + } + } + + *_domains = domains; + + return ret; +} + +static errno_t sss_tool_init(TALLOC_CTX *mem_ctx, + int *argc, const char **argv, + struct sss_tool_ctx **_tool_ctx) +{ + struct sss_tool_ctx *tool_ctx; + + tool_ctx = talloc_zero(mem_ctx, struct sss_tool_ctx); + if (tool_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed\n"); + return ENOMEM; + } + + sss_tool_common_opts(tool_ctx, argc, argv); + *_tool_ctx = tool_ctx; + + return EOK; +} + +static bool sss_tool_is_delimiter(struct sss_route_cmd *command) +{ + if (command->command != NULL && command->command[0] == '\0') { + return true; + } + + return false; +} + +static bool sss_tools_handles_init_error(struct sss_route_cmd *command, + errno_t init_err) +{ + if (init_err == EOK) { + return true; + } + + return command->handles_init_err == init_err; +} + +static size_t sss_tool_max_length(struct sss_route_cmd *commands) +{ + size_t max = 0; + size_t len; + int i; + + for (i = 0; commands[i].command != NULL; i++) { + if (sss_tool_is_delimiter(&commands[i])) { + continue; + } + + len = strlen(commands[i].command); + if (max < len) { + max = len; + } + } + + return max; +} + +static void sss_tool_usage(const char *tool_name, struct sss_route_cmd *commands) +{ + int min_len; + int i; + + ERROR("Usage:\n%s COMMAND COMMAND-ARGS\n\n", tool_name); + ERROR("Available commands:\n"); + + min_len = sss_tool_max_length(commands); + + for (i = 0; commands[i].command != NULL; i++) { + if (sss_tool_is_delimiter(&commands[i])) { + fprintf(stderr, "\n%s\n", commands[i].description); + continue; + } + + if (commands[i].description == NULL) { + fprintf(stderr, "* %40s\n", commands[i].command); + } else { + fprintf(stderr, "* %-*s\t %s\n", + min_len, commands[i].command, commands[i].description); + } + } + + ERROR("\n"); + sss_tool_print_common_opts(min_len); +} + +static int tool_cmd_init(struct sss_tool_ctx *tool_ctx, + struct sss_route_cmd *command) +{ + int ret; + uid_t uid; + + if (!(command->flags & SSS_TOOL_FLAG_SKIP_ROOT_CHECK)) { + uid = getuid(); + if (uid != 0) { + ERROR("'%s' must be run as root\n", command->command); + return EXIT_FAILURE; + } + } + + if (command->flags & SSS_TOOL_FLAG_SKIP_CMD_INIT) { + return EOK; + } + + /* Connect to confdb. */ + ret = sss_tool_confdb_init(tool_ctx, &tool_ctx->confdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to open confdb [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Setup domains. */ + ret = sss_tool_domains_init(tool_ctx, tool_ctx->confdb, &tool_ctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup domains [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(tool_ctx->confdb, tool_ctx, + CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_DEFAULT_DOMAIN, + NULL, &tool_ctx->default_domain); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get the default domain [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = EOK; + +done: + return ret; +} + +static errno_t sss_tool_route(int argc, const char **argv, + struct sss_tool_ctx *tool_ctx, + struct sss_route_cmd *commands, + void *pvt) +{ + struct sss_cmdline cmdline; + const char *cmd; + int i; + int ret; + + if (commands == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: commands can't be NULL!\n"); + return EINVAL; + } + + if (argc < 2) { + sss_tool_usage(argv[0], commands); + return EINVAL; + } + + cmd = argv[1]; + for (i = 0; commands[i].command != NULL; i++) { + if (sss_tool_is_delimiter(&commands[i])) { + continue; + } + + if (strcmp(commands[i].command, cmd) == 0) { + cmdline.exec = argv[0]; + cmdline.command = argv[1]; + cmdline.argc = argc - 2; + cmdline.argv = argv + 2; + + if (!tool_ctx->print_help) { + ret = tool_cmd_init(tool_ctx, &commands[i]); + + if (!sss_tools_handles_init_error(&commands[i], ret)) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Command %s does not handle initialization error [%d] %s\n", + cmdline.command, ret, sss_strerror(ret)); + return ret; + } + } + + return commands[i].fn(&cmdline, tool_ctx, pvt); + } + } + + sss_tool_usage(argv[0], commands); + return EINVAL; +} + +static struct poptOption *nonnull_popt_table(struct poptOption *options) +{ + static struct poptOption empty[] = { + POPT_TABLEEND + }; + + if (options == NULL) { + return empty; + } + + return options; +} + +errno_t sss_tool_popt_ex(struct sss_cmdline *cmdline, + struct poptOption *options, + enum sss_tool_opt require_option, + sss_popt_fn popt_fn, + void *popt_fn_pvt, + const char *fopt_name, + const char *fopt_help, + enum sss_tool_opt fopt_require, + const char **_fopt, + bool *_opt_set) +{ + struct poptOption opts_table[] = { + {NULL, '\0', POPT_ARG_INCLUDE_TABLE, nonnull_popt_table(options), \ + 0, _("Command options:"), NULL }, + {NULL, '\0', POPT_ARG_INCLUDE_TABLE, sss_tool_common_opts_table(), \ + 0, NULL, NULL }, + POPT_AUTOHELP + POPT_TABLEEND + }; + const char *fopt; + char *help; + poptContext pc; + bool opt_set; + int ret; + + /* Set output parameter _fopt to NULL value if present. */ + if (_fopt != NULL) { + *_fopt = NULL; + } + + /* Create help option string. We always need to append command name since + * we use POPT_CONTEXT_KEEP_FIRST. */ + if (fopt_name == NULL) { + help = talloc_asprintf(NULL, "%s %s %s", cmdline->exec, + cmdline->command, _("[OPTIONS...]")); + } else { + help = talloc_asprintf(NULL, "%s %s %s %s", cmdline->exec, + cmdline->command, fopt_name, _("[OPTIONS...]")); + } + if (help == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + return ENOMEM; + } + + /* Create popt context. This function is supposed to be called on + * command argv which does not contain executable (argv[0]), therefore + * we need to use KEEP_FIRST that ensures argv[0] is also processed. */ + pc = poptGetContext(cmdline->exec, cmdline->argc, cmdline->argv, + opts_table, POPT_CONTEXT_KEEP_FIRST); + + poptSetOtherOptionHelp(pc, help); + + /* Parse options. Invoke custom function if provided. If no parsing + * function is provided, print error on unknown option. */ + while ((ret = poptGetNextOpt(pc)) != -1) { + if (popt_fn != NULL) { + ret = popt_fn(pc, ret, popt_fn_pvt); + if (ret != EOK) { + goto done; + } + } else { + ERROR("Invalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(ret)); + poptPrintHelp(pc, stderr, 0); + ret = EINVAL; + goto done; + } + } + + /* Parse free option which is required if requested and fopt_require + * is SSS_TOOL_OPT_REQUIRED */ + opt_set = true; + fopt = poptGetArg(pc); + if (_fopt != NULL) { + if (fopt == NULL) { + if (fopt_require == SSS_TOOL_OPT_REQUIRED) { + ERROR("Missing option: %s\n\n", fopt_help); + poptPrintHelp(pc, stderr, 0); + ret = EINVAL; + goto done; + } + opt_set = false; + } + + /* No more arguments expected. If something follows it is an error. */ + if (poptGetArg(pc)) { + ERROR("Only one free argument is expected!\n\n"); + poptPrintHelp(pc, stderr, 0); + ret = EINVAL; + goto done; + } + + if (fopt != NULL) { + *_fopt = strdup(fopt); + if (*_fopt == NULL) { + ERROR("Out of memory!"); + ret = ENOMEM; + goto done; + } + } + } else if (_fopt == NULL && fopt != NULL) { + /* Unexpected free argument. */ + ERROR("Unexpected parameter: %s\n\n", fopt); + poptPrintHelp(pc, stderr, 0); + ret = EINVAL; + goto done; + } + + if ((_fopt != NULL && fopt_require == SSS_TOOL_OPT_REQUIRED && cmdline->argc < 2) + || cmdline->argc < 1) { + opt_set = false; + + /* If at least one option is required and not provided, print error. */ + if (require_option == SSS_TOOL_OPT_REQUIRED) { + ERROR("At least one option is required!\n\n"); + poptPrintHelp(pc, stderr, 0); + ret = EINVAL; + goto done; + } + } + + if (_opt_set != NULL) { + *_opt_set = opt_set; + } + + ret = EOK; + +done: + poptFreeContext(pc); + talloc_free(help); + if (ret != EOK && _fopt != NULL) { + free(discard_const(*_fopt)); + *_fopt = NULL; + } + + return ret; +} + +errno_t sss_tool_popt(struct sss_cmdline *cmdline, + struct poptOption *options, + enum sss_tool_opt require_option, + sss_popt_fn popt_fn, + void *popt_fn_pvt) +{ + return sss_tool_popt_ex(cmdline, options, require_option, + popt_fn, popt_fn_pvt, NULL, NULL, + SSS_TOOL_OPT_REQUIRED, NULL, NULL); +} + +int sss_tool_main(int argc, const char **argv, + struct sss_route_cmd *commands, + void *pvt) +{ + struct sss_tool_ctx *tool_ctx; + errno_t ret; + + ret = sss_tool_init(NULL, &argc, argv, &tool_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tool context\n"); + return EXIT_FAILURE; + } + + ret = sss_tool_route(argc, argv, tool_ctx, commands, pvt); + SYSDB_VERSION_ERROR(ret); + talloc_free(tool_ctx); + if (ret != EOK) { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +errno_t sss_tool_parse_name(TALLOC_CTX *mem_ctx, + struct sss_tool_ctx *tool_ctx, + const char *input, + const char **_username, + struct sss_domain_info **_domain) +{ + char *username = NULL; + char *domname = NULL; + struct sss_domain_info *domain; + int ret; + + ret = sss_parse_name_for_domains(mem_ctx, tool_ctx->domains, + tool_ctx->default_domain, input, + &domname, &username); + if (ret == EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to find domain. The domain name may " + "be a subdomain that was not yet found.\n"); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse name [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + domain = find_domain_by_name(tool_ctx->domains, domname, true); + + *_username = username; + *_domain = domain; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_zfree(username); + talloc_zfree(domname); + } + + return ret; +} + +errno_t sss_tool_connect_to_confdb(TALLOC_CTX *ctx, struct confdb_ctx **cdb_ctx) +{ + int ret; + char *confdb_path = NULL; + + confdb_path = talloc_asprintf(ctx, "%s/%s", DB_PATH, CONFDB_FILE); + if (confdb_path == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not allocate memory for confdb path\n"); + return ENOMEM; + } + + ret = confdb_init(ctx, cdb_ctx, confdb_path); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not initialize connection to the confdb\n"); + } + + talloc_free(confdb_path); + return ret; +} diff --git a/src/tools/common/sss_tools.h b/src/tools/common/sss_tools.h new file mode 100644 index 0000000..af49675 --- /dev/null +++ b/src/tools/common/sss_tools.h @@ -0,0 +1,105 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SSS_TOOLS_H_ +#define _SSS_TOOLS_H_ + +#include <talloc.h> +#include <popt.h> + +#include "confdb/confdb.h" + +struct sss_tool_ctx { + struct confdb_ctx *confdb; + + bool print_help; + char *default_domain; + struct sss_domain_info *domains; +}; + +struct sss_cmdline { + const char *exec; /* argv[0] */ + const char *command; /* command name */ + int argc; /* rest of arguments */ + const char **argv; +}; + +typedef errno_t +(*sss_route_fn)(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +#define SSS_TOOL_COMMAND_FLAGS(cmd, msg, err, fn, flags) \ + {cmd, _(msg), err, fn, flags} +#define SSS_TOOL_COMMAND(cmd, msg, err, fn) \ + {cmd, _(msg), err, fn, 0} +#define SSS_TOOL_COMMAND_NOMSG(cmd, err, fn) {cmd, NULL, err, fn, 0} +#define SSS_TOOL_DELIMITER(message) {"", _(message), 0, NULL, 0} +#define SSS_TOOL_LAST {NULL, NULL, 0, NULL, 0} + +#define SSS_TOOL_FLAG_SKIP_CMD_INIT 0x01 +#define SSS_TOOL_FLAG_SKIP_ROOT_CHECK 0x02 + +struct sss_route_cmd { + const char *command; + const char *description; + errno_t handles_init_err; + sss_route_fn fn; + int flags; +}; + +typedef errno_t (*sss_popt_fn)(poptContext pc, char option, void *pvt); + +enum sss_tool_opt { + SSS_TOOL_OPT_REQUIRED, + SSS_TOOL_OPT_OPTIONAL +}; + +errno_t sss_tool_popt_ex(struct sss_cmdline *cmdline, + struct poptOption *options, + enum sss_tool_opt require_option, + sss_popt_fn popt_fn, + void *popt_fn_pvt, + const char *fopt_name, + const char *fopt_help, + enum sss_tool_opt fopt_require, + const char **_fopt, + bool *_opt_set); + +errno_t sss_tool_popt(struct sss_cmdline *cmdline, + struct poptOption *options, + enum sss_tool_opt require_option, + sss_popt_fn popt_fn, + void *popt_fn_pvt); + +int sss_tool_main(int argc, const char **argv, + struct sss_route_cmd *commands, + void *pvt); + +errno_t sss_tool_parse_name(TALLOC_CTX *mem_ctx, + struct sss_tool_ctx *tool_ctx, + const char *input, + const char **_username, + struct sss_domain_info **_domain); + + +errno_t sss_tool_connect_to_confdb(TALLOC_CTX *ctx, struct confdb_ctx **cdb_ctx); + +#endif /* SRC_TOOLS_COMMON_SSS_TOOLS_H_ */ diff --git a/src/tools/sss_cache.c b/src/tools/sss_cache.c new file mode 100644 index 0000000..79de13a --- /dev/null +++ b/src/tools/sss_cache.c @@ -0,0 +1,1028 @@ +/* + SSSD + + sss_cache + + Copyright (C) Jan Zeleny <jzeleny@redhat.com> 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <talloc.h> +#include <popt.h> +#include <sys/types.h> + +#include "util/util.h" +#include "tools/tools_util.h" +#include "db/sysdb.h" +#include "db/sysdb_services.h" +#include "db/sysdb_autofs.h" +#include "db/sysdb_ssh.h" +#include "db/sysdb_sudo.h" + +#define INVALIDATE_NONE 0 +#define INVALIDATE_USERS 1 +#define INVALIDATE_GROUPS 2 +#define INVALIDATE_NETGROUPS 4 +#define INVALIDATE_SERVICES 8 +#define INVALIDATE_AUTOFSMAPS 16 +#define INVALIDATE_SSH_HOSTS 32 +#define INVALIDATE_SUDO_RULES 64 + +#ifdef BUILD_AUTOFS +#ifdef BUILD_SSH +#define INVALIDATE_EVERYTHING (INVALIDATE_USERS | INVALIDATE_GROUPS | \ + INVALIDATE_NETGROUPS | INVALIDATE_SERVICES | \ + INVALIDATE_AUTOFSMAPS | INVALIDATE_SSH_HOSTS ) +#else /* BUILD_SSH */ +#define INVALIDATE_EVERYTHING (INVALIDATE_USERS | INVALIDATE_GROUPS | \ + INVALIDATE_NETGROUPS | INVALIDATE_SERVICES | \ + INVALIDATE_AUTOFSMAPS ) +#endif /* BUILD_SSH */ +#else /* BUILD_AUTOFS */ +#ifdef BUILD_SSH +#define INVALIDATE_EVERYTHING (INVALIDATE_USERS | INVALIDATE_GROUPS | \ + INVALIDATE_NETGROUPS | INVALIDATE_SERVICES | \ + INVALIDATE_SSH_HOSTS ) +#else /* BUILD_SSH */ +#define INVALIDATE_EVERYTHING (INVALIDATE_USERS | INVALIDATE_GROUPS | \ + INVALIDATE_NETGROUPS | INVALIDATE_SERVICES ) +#endif /* BUILD_SSH */ +#endif /* BUILD_AUTOFS */ + +enum sss_cache_entry { + TYPE_USER=0, + TYPE_GROUP, + TYPE_NETGROUP, + TYPE_SERVICE, + TYPE_AUTOFSMAP, + TYPE_SSH_HOST, + TYPE_SUDO_RULE +}; + +static errno_t search_autofsmaps(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *sub_filter, const char **attrs, + size_t *msgs_count, struct ldb_message ***msgs); + +struct input_values { + char *domain; + char *group; + char *map; + char *netgroup; + char *service; + char *ssh_host; + char *sudo_rule; + char *user; +}; + +struct cache_tool_ctx { + struct confdb_ctx *confdb; + struct sss_domain_info *domains; + + char *user_filter; + char *group_filter; + char *netgroup_filter; + char *service_filter; + char *autofs_filter; + char *ssh_host_filter; + char *sudo_rule_filter; + + char *user_name; + char *group_name; + char *netgroup_name; + char *service_name; + char *autofs_name; + char *ssh_host_name; + char *sudo_rule_name; + + bool update_user_filter; + bool update_group_filter; + bool update_netgroup_filter; + bool update_service_filter; + bool update_autofs_filter; + bool update_ssh_host_filter; + bool update_sudo_rule_filter; +}; + +static void free_input_values(struct input_values *values); +static bool is_filter_valid(struct cache_tool_ctx *ctx, + struct input_values *values, int idb); +static errno_t init_domains(struct cache_tool_ctx *ctx, + const char *domain); +static errno_t init_context(int argc, const char *argv[], + struct cache_tool_ctx **tctx); +static errno_t invalidate_entry(TALLOC_CTX *ctx, + struct sss_domain_info *domain, + const char *name, int entry_type); +static bool invalidate_entries(TALLOC_CTX *ctx, + struct sss_domain_info *dinfo, + enum sss_cache_entry entry_type, + const char *filter, const char *name); +static errno_t update_all_filters(struct cache_tool_ctx *tctx, + struct sss_domain_info *dinfo); +static int sysdb_invalidate_user_cache_entry(struct sss_domain_info *domain, + const char *name); +static int sysdb_invalidate_group_cache_entry(struct sss_domain_info *domain, + const char *name); + +int main(int argc, const char *argv[]) +{ + errno_t ret; + struct cache_tool_ctx *tctx = NULL; + struct sysdb_ctx *sysdb; + bool skipped = true; + struct sss_domain_info *dinfo; + + /* If systemd is in offline mode, + * there's not going to be a sssd instance + * running. This occurs for both e.g. yum --installroot + * as well as rpm-ostree offline updates. + * + * So let's just quickly do nothing. (Though note today + * yum --installroot doesn't set this variable, rpm-ostree + * does) + * + * For more information on the variable, see: + * https://github.com/systemd/systemd/pull/7631 + */ + const char *systemd_offline = getenv ("SYSTEMD_OFFLINE"); + if (systemd_offline && strcmp (systemd_offline, "1") == 0) { + return 0; + } + + ret = init_context(argc, argv, &tctx); + if (ret == ERR_NO_DOMAIN_ENABLED) { + /* nothing to invalidate; no reason to fail */ + ret = EOK; + goto done; + } else if (ret == ERR_DOMAIN_NOT_FOUND) { + /* Cannot find domain specified in the parameter --domain. + * It might be a typo and therefore we will fail. + */ + ret = ENOENT; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error initializing context for the application\n"); + goto done; + } + + for (dinfo = tctx->domains; dinfo; + dinfo = get_next_domain(dinfo, SSS_GND_DESCEND)) { + if (!IS_SUBDOMAIN(dinfo)) { + /* Update list of subdomains for this domain */ + ret = sysdb_update_subdomains(dinfo, tctx->confdb); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to update subdomains for domain %s.\n", dinfo->name); + } + } + + sysdb = dinfo->sysdb; + /* Update filters for each domain */ + ret = update_all_filters(tctx, dinfo); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to update filters.\n"); + goto done; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not start the transaction!\n"); + goto done; + } + + skipped &= !invalidate_entries(tctx, dinfo, TYPE_USER, + tctx->user_filter, + tctx->user_name); + skipped &= !invalidate_entries(tctx, dinfo, TYPE_GROUP, + tctx->group_filter, + tctx->group_name); + skipped &= !invalidate_entries(tctx, dinfo, TYPE_NETGROUP, + tctx->netgroup_filter, + tctx->netgroup_name); + skipped &= !invalidate_entries(tctx, dinfo, TYPE_SERVICE, + tctx->service_filter, + tctx->service_name); + skipped &= !invalidate_entries(tctx, dinfo, TYPE_AUTOFSMAP, + tctx->autofs_filter, + tctx->autofs_name); + skipped &= !invalidate_entries(tctx, dinfo, TYPE_SSH_HOST, + tctx->ssh_host_filter, + tctx->ssh_host_name); + skipped &= !invalidate_entries(tctx, dinfo, TYPE_SUDO_RULE, + tctx->sudo_rule_filter, + tctx->sudo_rule_name); + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not commit the transaction!\n"); + ret = sysdb_transaction_cancel(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to cancel transaction\n"); + } + } + } + + if (skipped == true) { + ERROR("No cache object matched the specified search\n"); + ret = ENOENT; + goto done; + } else { + ret = sss_memcache_clear_all(); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to clear memory cache.\n"); + goto done; + } + } + + ret = EOK; +done: + if (tctx) talloc_free(tctx); + return ret; +} + +static void free_input_values(struct input_values *values) +{ + free(values->domain); + free(values->group); + free(values->map); + free(values->netgroup); + free(values->service); + free(values->ssh_host); + free(values->sudo_rule); + free(values->user); +} + +static errno_t update_filter(struct cache_tool_ctx *tctx, + struct sss_domain_info *dinfo, + char *name, bool update, const char *fmt, + enum sss_cache_entry entry_type, + bool force_case_sensitivity, + char **_filter) +{ + errno_t ret; + char *parsed_domain = NULL; + char *parsed_name = NULL; + TALLOC_CTX *tmp_ctx = NULL; + char *use_name = NULL; + char *filter; + char *sanitized; + char *lc_sanitized; + + if (!name || !update) { + /* Nothing to do */ + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory.\n"); + return ENOMEM; + } + + ret = sss_parse_name(tmp_ctx, dinfo->names, name, + &parsed_domain, &parsed_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_parse_name failed\n"); + goto done; + } + + if (parsed_domain != NULL && strcasecmp(dinfo->name, parsed_domain) != 0) { + /* We were able to parse the domain from given fqdn, but it + * does not match with currently processed domain. */ + filter = NULL; + ret = EOK; + goto done; + } + + if (!dinfo->case_sensitive && !force_case_sensitivity) { + use_name = sss_tc_utf8_str_tolower(tmp_ctx, parsed_name); + if (!use_name) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory\n"); + ret = ENOMEM; + goto done; + } + } else { + use_name = parsed_name; + } + + switch (entry_type) { + case TYPE_USER: + case TYPE_GROUP: + use_name = sss_create_internal_fqname(tmp_ctx, use_name, dinfo->name); + default: + break; + } + if (!use_name) { + ret = ENOMEM; + goto done; + } + + ret = sss_filter_sanitize_for_dom(tmp_ctx, use_name, dinfo, + &sanitized, &lc_sanitized); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to sanitize the given name.\n"); + goto done; + } + + if (fmt) { + if (!dinfo->case_sensitive && !force_case_sensitivity) { + filter = talloc_asprintf(tmp_ctx, "(|(%s=%s)(%s=%s))", + SYSDB_NAME_ALIAS, lc_sanitized, + SYSDB_NAME_ALIAS, sanitized); + } else { + filter = talloc_asprintf(tmp_ctx, fmt, SYSDB_NAME, sanitized); + } + } else { + filter = talloc_strdup(tmp_ctx, sanitized); + } + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory\n"); + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + talloc_free(*_filter); + *_filter = talloc_steal(tctx, filter); + } + + talloc_free(tmp_ctx); + return ret; + +} + +/* This function updates all filters for specified domain using this + * domains regex to parse string into domain and name (if exists). */ +static errno_t update_all_filters(struct cache_tool_ctx *tctx, + struct sss_domain_info *dinfo) +{ + errno_t ret; + + /* Update user filter */ + ret = update_filter(tctx, dinfo, tctx->user_name, + tctx->update_user_filter, "(%s=%s)", + TYPE_USER, false, + &tctx->user_filter); + if (ret != EOK) { + return ret; + } + + /* Update group filter */ + ret = update_filter(tctx, dinfo, tctx->group_name, + tctx->update_group_filter, "(%s=%s)", + TYPE_GROUP, false, + &tctx->group_filter); + if (ret != EOK) { + return ret; + } + + /* Update netgroup filter */ + ret = update_filter(tctx, dinfo, tctx->netgroup_name, + tctx->update_netgroup_filter, "(%s=%s)", + TYPE_NETGROUP, false, + &tctx->netgroup_filter); + if (ret != EOK) { + return ret; + } + + /* Update service filter */ + ret = update_filter(tctx, dinfo, tctx->service_name, + tctx->update_service_filter, "(%s=%s)", + TYPE_SERVICE, false, + &tctx->service_filter); + if (ret != EOK) { + return ret; + } + + /* Update autofs filter */ + ret = update_filter(tctx, dinfo, tctx->autofs_name, + tctx->update_autofs_filter, + "(&(objectclass="SYSDB_AUTOFS_MAP_OC")(%s=%s))", + TYPE_AUTOFSMAP, true, + &tctx->autofs_filter); + if (ret != EOK) { + return ret; + } + + /* Update ssh host filter */ + ret = update_filter(tctx, dinfo, tctx->ssh_host_name, + tctx->update_ssh_host_filter, "(%s=%s)", + TYPE_SSH_HOST, false, + &tctx->ssh_host_filter); + if (ret != EOK) { + return ret; + } + + /* Update sudo rule filter */ + ret = update_filter(tctx, dinfo, tctx->sudo_rule_name, + tctx->update_sudo_rule_filter, + "(%s=%s)", TYPE_SUDO_RULE, false, + &tctx->sudo_rule_filter); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +static bool invalidate_entries(TALLOC_CTX *ctx, + struct sss_domain_info *dinfo, + enum sss_cache_entry entry_type, + const char *filter, const char *name) +{ + const char *attrs[] = {SYSDB_NAME, NULL}; + size_t msg_count = 0; + struct ldb_message **msgs; + const char *type_string = "unknown"; + errno_t ret = EINVAL; + int i; + const char *c_name; + bool iret; + + if (!filter) return false; + switch (entry_type) { + case TYPE_USER: + type_string = "user"; + ret = sysdb_search_users(ctx, dinfo, + filter, attrs, &msg_count, &msgs); + break; + case TYPE_GROUP: + type_string = "group"; + ret = sysdb_search_groups(ctx, dinfo, + filter, attrs, &msg_count, &msgs); + break; + case TYPE_NETGROUP: + type_string = "netgroup"; + ret = sysdb_search_netgroups(ctx, dinfo, + filter, attrs, &msg_count, &msgs); + break; + case TYPE_SERVICE: + type_string = "service"; + ret = sysdb_search_services(ctx, dinfo, + filter, attrs, &msg_count, &msgs); + break; + case TYPE_AUTOFSMAP: + type_string = "autofs map"; + ret = search_autofsmaps(ctx, dinfo, filter, attrs, &msg_count, &msgs); + break; + case TYPE_SSH_HOST: + type_string = "ssh_host"; +#ifdef BUILD_SSH + ret = sysdb_search_ssh_hosts(ctx, dinfo, + filter, attrs, &msg_count, &msgs); +#else /* BUILD_SSH */ + ret = ENOSYS; +#endif /* BUILD_SSH */ + break; + case TYPE_SUDO_RULE: + type_string = "sudo_rule"; +#ifdef BUILD_SUDO + ret = sysdb_search_sudo_rules(ctx, dinfo, + filter, attrs, &msg_count, &msgs); +#else /* BUILD_SUDO */ + ret = ENOSYS; +#endif /* BUILD_SUDO */ + break; + } + + if (ret != EOK) { + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, "'%s' %s: Not found in domain '%s'\n", + type_string, name ? name : "", dinfo->name); + if (name == NULL) { + /* nothing to invalidate in that domain, no reason to fail */ + return true; + } else { + /* we failed to invalidate explicit name; inform about it */ + return false; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Searching for %s in domain %s with filter %s failed\n", + type_string, dinfo->name, filter); + } + return false; + } + + iret = true; + for (i = 0; i < msg_count; i++) { + c_name = ldb_msg_find_attr_as_string(msgs[i], SYSDB_NAME, NULL); + if (c_name == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Something bad happened, can't find attribute %s\n", + SYSDB_NAME); + ERROR("Couldn't invalidate %1$s\n", type_string); + iret = false; + } else { + ret = invalidate_entry(ctx, dinfo, c_name, entry_type); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Couldn't invalidate %s %s\n", type_string, c_name); + ERROR("Couldn't invalidate %1$s %2$s\n", type_string, c_name); + iret = false; + } + } + } + talloc_zfree(msgs); + return iret; +} + +static errno_t invalidate_entry(TALLOC_CTX *ctx, + struct sss_domain_info *domain, + const char *name, int entry_type) +{ + struct sysdb_attrs *sys_attrs = NULL; + errno_t ret; + + sys_attrs = sysdb_new_attrs(ctx); + if (sys_attrs) { + ret = sysdb_attrs_add_time_t(sys_attrs, + SYSDB_CACHE_EXPIRE, 1); + if (ret == EOK) { + switch (entry_type) { + case TYPE_USER: + /* For users, we also need to reset the initgroups + * cache expiry */ + ret = sysdb_attrs_add_time_t(sys_attrs, + SYSDB_INITGR_EXPIRE, 1); + if (ret != EOK) return ret; + ret = sysdb_attrs_add_string(sys_attrs, + SYSDB_ORIG_MODSTAMP, "1"); + if (ret != EOK) return ret; + ret = sysdb_attrs_add_uint32(sys_attrs, + SYSDB_USN, 1); + if (ret != EOK) return ret; + + ret = sysdb_set_user_attr(domain, name, sys_attrs, + SYSDB_MOD_REP); + if (ret != EOK) break; + + /* WARNING: Direct writing to persistent cache!! */ + ret = sysdb_invalidate_user_cache_entry(domain, name); + break; + case TYPE_GROUP: + ret = sysdb_attrs_add_string(sys_attrs, + SYSDB_ORIG_MODSTAMP, "1"); + if (ret != EOK) return ret; + ret = sysdb_attrs_add_uint32(sys_attrs, + SYSDB_USN, 1); + if (ret != EOK) return ret; + + ret = sysdb_set_group_attr(domain, name, sys_attrs, + SYSDB_MOD_REP); + if (ret != EOK) break; + + /* WARNING: Direct writing to persistent cache!! */ + ret = sysdb_invalidate_group_cache_entry(domain, name); + break; + case TYPE_NETGROUP: + ret = sysdb_set_netgroup_attr(domain, name, sys_attrs, + SYSDB_MOD_REP); + break; + case TYPE_SERVICE: + ret = sysdb_set_service_attr(domain, name, + sys_attrs, SYSDB_MOD_REP); + break; + case TYPE_AUTOFSMAP: + /* For users, we also need to reset the enumeration + * expiration time. */ + ret = sysdb_attrs_add_time_t(sys_attrs, + SYSDB_ENUM_EXPIRE, 1); + if (ret != EOK) { + return ret; + } + + ret = sysdb_set_autofsmap_attr(domain, name, + sys_attrs, SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not invalidate " + "autofs map %s\n", name); + break; + } + + ret = sysdb_invalidate_autofs_entries(domain, name); + break; + case TYPE_SSH_HOST: +#ifdef BUILD_SSH + ret = sysdb_set_ssh_host_attr(domain, name, + sys_attrs, SYSDB_MOD_REP); +#else /* BUILD_SSH */ + ret = ENOSYS; +#endif /* BUILD_SSH */ + break; + case TYPE_SUDO_RULE: +#ifdef BUILD_SUDO + ret = sysdb_set_sudo_rule_attr(domain, name, + sys_attrs, SYSDB_MOD_REP); +#else /* BUILD_SUDO */ + ret = ENOSYS; +#endif /* BUILD_SUDO */ + break; + default: + return EINVAL; + } + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not set entry attributes\n"); + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not add expiration time to attributes\n"); + } + talloc_zfree(sys_attrs); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not create sysdb attributes\n"); + ret = ENOMEM; + } + return ret; +} + +static errno_t init_domains(struct cache_tool_ctx *ctx, + const char *domain) +{ + char *confdb_path; + int ret; + struct sss_domain_info *dinfo; + + confdb_path = talloc_asprintf(ctx, "%s/%s", DB_PATH, CONFDB_FILE); + if (confdb_path == NULL) { + return ENOMEM; + } + + /* Connect to the conf db */ + ret = confdb_init(ctx, &ctx->confdb, confdb_path); + talloc_free(confdb_path); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not initialize connection to the confdb\n"); + return ret; + } + + if (domain) { + ret = sssd_domain_init(ctx, ctx->confdb, + domain, DB_PATH, &ctx->domains); + if (ret != EOK) { + SYSDB_VERSION_ERROR(ret); + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not initialize connection to the sysdb\n"); + return ret; + } + + } else { + ret = confdb_get_domains(ctx->confdb, &ctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not initialize domains\n"); + return ret; + } + + ret = sysdb_init(ctx, ctx->domains); + SYSDB_VERSION_ERROR(ret); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not initialize connection to the sysdb\n"); + return ret; + } + } + + for (dinfo = ctx->domains; dinfo; dinfo = get_next_domain(dinfo, 0)) { + ret = sss_names_init(ctx, ctx->confdb, dinfo->name, &dinfo->names); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_names_init() failed\n"); + return ret; + } + } + + return EOK; +} + +static errno_t init_context(int argc, const char *argv[], + struct cache_tool_ctx **tctx) +{ + struct cache_tool_ctx *ctx = NULL; + int idb = INVALIDATE_NONE; + struct input_values values = { 0 }; + int debug = SSSDBG_TOOLS_DEFAULT; + errno_t ret = EOK; + + poptContext pc = NULL; + struct poptOption long_options[] = { + POPT_AUTOHELP + { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &debug, + 0, _("The debug level to run with"), NULL }, + { "everything", 'E', POPT_ARG_NONE, NULL, 'e', + _("Invalidate all cached entries"), NULL }, + { "user", 'u', POPT_ARG_STRING, &(values.user), 0, + _("Invalidate particular user"), NULL }, + { "users", 'U', POPT_ARG_NONE, NULL, 'u', + _("Invalidate all users"), NULL }, + { "group", 'g', POPT_ARG_STRING, &(values.group), 0, + _("Invalidate particular group"), NULL }, + { "groups", 'G', POPT_ARG_NONE, NULL, 'g', + _("Invalidate all groups"), NULL }, + { "netgroup", 'n', POPT_ARG_STRING, &(values.netgroup), 0, + _("Invalidate particular netgroup"), NULL }, + { "netgroups", 'N', POPT_ARG_NONE, NULL, 'n', + _("Invalidate all netgroups"), NULL }, + { "service", 's', POPT_ARG_STRING, &(values.service), 0, + _("Invalidate particular service"), NULL }, + { "services", 'S', POPT_ARG_NONE, NULL, 's', + _("Invalidate all services"), NULL }, +#ifdef BUILD_AUTOFS + { "autofs-map", 'a', POPT_ARG_STRING, &(values.map), 0, + _("Invalidate particular autofs map"), NULL }, + { "autofs-maps", 'A', POPT_ARG_NONE, NULL, 'a', + _("Invalidate all autofs maps"), NULL }, +#endif /* BUILD_AUTOFS */ +#ifdef BUILD_SSH + { "ssh-host", 'h', POPT_ARG_STRING, &(values.ssh_host), 0, + _("Invalidate particular SSH host"), NULL }, + { "ssh-hosts", 'H', POPT_ARG_NONE, NULL, 'h', + _("Invalidate all SSH hosts"), NULL }, +#endif /* BUILD_SSH */ +#ifdef BUILD_SUDO + { "sudo-rule", 'r', POPT_ARG_STRING, &(values.sudo_rule), 0, + _("Invalidate particular sudo rule"), NULL }, + { "sudo-rules", 'R', POPT_ARG_NONE, NULL, 'r', + _("Invalidate all cached sudo rules"), NULL }, +#endif /* BUILD_SUDO */ + { "domain", 'd', POPT_ARG_STRING, &(values.domain), 0, + _("Only invalidate entries from a particular domain"), NULL }, + POPT_TABLEEND + }; + + ret = set_locale(); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "set_locale failed (%d): %s\n", ret, strerror(ret)); + ERROR("Error setting the locale\n"); + goto fini; + } + + pc = poptGetContext(NULL, argc, argv, long_options, 0); + while ((ret = poptGetNextOpt(pc)) > 0) { + switch (ret) { + case 'u': + idb |= INVALIDATE_USERS; + break; + case 'g': + idb |= INVALIDATE_GROUPS; + break; + case 'n': + idb |= INVALIDATE_NETGROUPS; + break; + case 's': + idb |= INVALIDATE_SERVICES; + break; + case 'a': + idb |= INVALIDATE_AUTOFSMAPS; + break; + case 'h': + idb |= INVALIDATE_SSH_HOSTS; + break; + case 'r': + idb |= INVALIDATE_SUDO_RULES; + break; + case 'e': + idb = INVALIDATE_EVERYTHING; +#ifdef BUILD_SUDO + idb |= INVALIDATE_SUDO_RULES; +#endif /* BUILD_SUDO */ + break; + } + } + + DEBUG_CLI_INIT(debug); + debug_prg_name = argv[0]; + + if (ret != -1) { + BAD_POPT_PARAMS(pc, poptStrerror(ret), ret, fini); + } + + if (poptGetArg(pc)) { + BAD_POPT_PARAMS(pc, + _("Unexpected argument(s) provided, options that " + "invalidate a single object only accept a single " + "provided argument.\n"), + ret, fini); + } + + if (idb == INVALIDATE_NONE && !values.user && !values.group && + !values.netgroup && !values.service && !values.map && + !values.ssh_host && !values.sudo_rule) { + BAD_POPT_PARAMS(pc, + _("Please select at least one object to invalidate\n"), + ret, fini); + } + + CHECK_ROOT(ret, debug_prg_name); + + ctx = talloc_zero(NULL, struct cache_tool_ctx); + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not allocate memory for tools context\n"); + ret = ENOMEM; + goto fini; + } + + if (idb & INVALIDATE_USERS) { + ctx->user_filter = talloc_asprintf(ctx, "(%s=*)", SYSDB_NAME); + ctx->update_user_filter = false; + } else if (values.user) { + ctx->user_name = talloc_strdup(ctx, values.user); + ctx->update_user_filter = true; + } + + if (idb & INVALIDATE_GROUPS) { + ctx->group_filter = talloc_asprintf(ctx, "(%s=*)", SYSDB_NAME); + ctx->update_group_filter = false; + } else if (values.group) { + ctx->group_name = talloc_strdup(ctx, values.group); + ctx->update_group_filter = true; + } + + if (idb & INVALIDATE_NETGROUPS) { + ctx->netgroup_filter = talloc_asprintf(ctx, "(%s=*)", SYSDB_NAME); + ctx->update_netgroup_filter = false; + } else if (values.netgroup) { + ctx->netgroup_name = talloc_strdup(ctx, values.netgroup); + ctx->update_netgroup_filter = true; + } + + if (idb & INVALIDATE_SERVICES) { + ctx->service_filter = talloc_asprintf(ctx, "(%s=*)", SYSDB_NAME); + ctx->update_service_filter = false; + } else if (values.service) { + ctx->service_name = talloc_strdup(ctx, values.service); + ctx->update_service_filter = true; + } + + if (idb & INVALIDATE_AUTOFSMAPS) { + ctx->autofs_filter = talloc_asprintf(ctx, "(&(objectclass=%s)(%s=*))", + SYSDB_AUTOFS_MAP_OC, SYSDB_NAME); + ctx->update_autofs_filter = false; + } else if (values.map) { + ctx->autofs_name = talloc_strdup(ctx, values.map); + ctx->update_autofs_filter = true; + } + + if (idb & INVALIDATE_SSH_HOSTS) { + ctx->ssh_host_filter = talloc_asprintf(ctx, "(%s=*)", SYSDB_NAME); + ctx->update_ssh_host_filter = false; + } else if (values.ssh_host) { + ctx->ssh_host_name = talloc_strdup(ctx, values.ssh_host); + ctx->update_ssh_host_filter = true; + } + + if (idb & INVALIDATE_SUDO_RULES) { + ctx->sudo_rule_filter = talloc_asprintf(ctx, "(%s=*)", SYSDB_NAME); + ctx->update_sudo_rule_filter = false; + } else if (values.sudo_rule) { + ctx->sudo_rule_name = talloc_strdup(ctx, values.sudo_rule); + ctx->update_sudo_rule_filter = true; + } + + if (is_filter_valid(ctx, &values, idb) == false) { + DEBUG(SSSDBG_CRIT_FAILURE, "Construction of filters failed\n"); + ret = ENOMEM; + goto fini; + } + + ret = init_domains(ctx, values.domain); + if (ret == ERR_NO_DOMAIN_ENABLED && values.domain == NULL) { + /* Nothing to invalidate; do not log confusing messages. */ + goto fini; + } else if (ret != EOK) { + if (values.domain) { + ERROR("Could not open domain %1$s. If the domain is a subdomain " + "(trusted domain), use fully qualified name instead of " + "--domain/-d parameter.\n", values.domain); + ret = ERR_DOMAIN_NOT_FOUND; + } else { + ERROR("Could not open available domains\n"); + } + DEBUG(SSSDBG_OP_FAILURE, + "Initialization of sysdb connections failed\n"); + goto fini; + } + + ret = EOK; + +fini: + poptFreeContext(pc); + free_input_values(&values); + if (ret != EOK && ctx) { + talloc_zfree(ctx); + } + if (ret == EOK) { + *tctx = ctx; + } + return ret; +} + +static bool is_filter_valid(struct cache_tool_ctx *ctx, + struct input_values *values, int idb) +{ + if ((idb & INVALIDATE_USERS) && ctx->user_filter == NULL) { + return false; + } + + if ((idb & INVALIDATE_GROUPS) && ctx->group_filter == NULL) { + return false; + } + + if ((idb & INVALIDATE_NETGROUPS) && ctx->netgroup_filter == NULL) { + return false; + } + + if ((idb & INVALIDATE_SERVICES) && ctx->service_filter == NULL) { + return false; + } + + if ((idb & INVALIDATE_AUTOFSMAPS) && ctx->autofs_filter == NULL) { + return false; + } + + if ((idb & INVALIDATE_SSH_HOSTS) && ctx->ssh_host_filter == NULL) { + return false; + } + + if (values->user && ctx->user_name == NULL) { + return false; + } + + if (values->group && ctx->group_name == NULL) { + return false; + } + + if (values->netgroup && ctx->netgroup_name == NULL) { + return false; + } + + if (values->service && ctx->service_name == NULL) { + return false; + } + + if (values->map && ctx->autofs_name == NULL) { + return false; + } + + if (values->ssh_host && ctx->ssh_host_name == NULL) { + return false; + } + + if (values->sudo_rule && ctx->sudo_rule_name == NULL) { + return false; + } + + return true; +} + +static errno_t +search_autofsmaps(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *sub_filter, const char **attrs, + size_t *msgs_count, struct ldb_message ***msgs) +{ +#ifdef BUILD_AUTOFS + return sysdb_search_custom(mem_ctx, domain, sub_filter, + AUTOFS_MAP_SUBDIR, attrs, + msgs_count, msgs); +#else + return ENOSYS; +#endif /* BUILD_AUTOFS */ +} + +/* WARNING: Direct writing to persistent cache!! */ +static int sysdb_invalidate_user_cache_entry(struct sss_domain_info *domain, + const char *name) +{ + return sysdb_invalidate_cache_entry(domain, name, true); +} + +/* WARNING: Direct writing to persistent cache!! */ +static int sysdb_invalidate_group_cache_entry(struct sss_domain_info *domain, + const char *name) +{ + return sysdb_invalidate_cache_entry(domain, name, false); +} diff --git a/src/tools/sss_obfuscate b/src/tools/sss_obfuscate new file mode 100644 index 0000000..5981e81 --- /dev/null +++ b/src/tools/sss_obfuscate @@ -0,0 +1,123 @@ +#!/usr/bin/python + +from __future__ import print_function + +import sys +from optparse import OptionParser + +import pysss +import SSSDConfig +import getpass + + +def parse_options(): + parser = OptionParser() + parser.set_description("sss_obfuscate converts a given password into \ + human-unreadable format and places it into \ + appropriate domain section of the SSSD config \ + file. The password can be passed in by stdin, \ + specified on the command-line or entered \ + interactively") + parser.add_option("-s", "--stdin", action="store_true", + dest="stdin", default=False, + help="Read the password from stdin.") + parser.add_option("-d", "--domain", + dest="domain", default=None, + help="The domain to use the password in (mandatory)", + metavar="DOMNAME") + parser.add_option("-f", "--file", + dest="filename", default=None, + help="Set input file to FILE (default: Use system " + "default, usually /etc/sssd/sssd.conf)", + metavar="FILE") + (options, args) = parser.parse_args() + + return options, args + + +def main(): + options, args = parse_options() + if not options: + print("Cannot parse options", file=sys.stderr) + return 1 + + if not options.domain: + print("No domain specified", file=sys.stderr) + return 1 + + if not options.stdin: + try: + pprompt = lambda: (getpass.getpass("Enter password: "), + getpass.getpass("Re-enter password: ")) + p1, p2 = pprompt() + + # Work around bug in Python 2.6 + if '\x03' in p1 or '\x03' in p2: + raise KeyboardInterrupt + + while p1 != p2: + print('Passwords do not match. Try again') + p1, p2 = pprompt() + + # Work around bug in Python 2.6 + if '\x03' in p1 or '\x03' in p2: + raise KeyboardInterrupt + password = p1 + + except EOFError: + print('\nUnexpected end-of-file. Password change aborted', + file=sys.stderr) + return 1 + except KeyboardInterrupt: + return 1 + + else: + try: + password = sys.stdin.read() + except KeyboardInterrupt: + return 1 + + # Obfuscate the password + obfobj = pysss.password() + obfpwd = obfobj.encrypt(password, obfobj.AES_256) + + # Save the obfuscated password into the domain + try: + sssdconfig = SSSDConfig.SSSDConfig() + except IOError: + print("Cannot read internal configuration files.") + return 1 + try: + sssdconfig.import_config(options.filename) + except IOError: + print("Permissions error reading config file") + return 1 + + try: + domain = sssdconfig.get_domain(options.domain) + except SSSDConfig.NoDomainError: + print("No such domain %s" % options.domain) + return 1 + + try: + domain.set_option('ldap_default_authtok_type', 'obfuscated_password') + domain.set_option('ldap_default_authtok', obfpwd) + except SSSDConfig.NoOptionError: + print("The domain %s does not seem to support the required options" + % options.domain) + return 1 + + sssdconfig.save_domain(domain) + try: + sssdconfig.write() + except IOError: + # File could not be written + print("Could not write to config file. Check that you have the " + "appropriate permissions to edit this file.", file=sys.stderr) + return 1 + + return 0 + +if __name__ == "__main__": + ret = main() + sys.exit(ret) diff --git a/src/tools/sss_override.c b/src/tools/sss_override.c new file mode 100644 index 0000000..cfd8f17 --- /dev/null +++ b/src/tools/sss_override.c @@ -0,0 +1,1975 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdlib.h> + +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "db/sysdb.h" +#include "tools/common/sss_tools.h" +#include "tools/common/sss_colondb.h" + +#define LOCALVIEW SYSDB_LOCAL_VIEW_NAME +#define ORIGNAME "originalName" + +struct override_user { + const char *input_name; + const char *orig_name; + const char *sysdb_name; + struct sss_domain_info *domain; + + const char *name; + uid_t uid; + gid_t gid; + const char *home; + const char *shell; + const char *gecos; + const char *cert; +}; + +struct override_group { + const char *input_name; + const char *orig_name; + const char *sysdb_name; + struct sss_domain_info *domain; + + const char *name; + gid_t gid; +}; + +static errno_t parse_cmdline(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + struct poptOption *options, + const char **_input_name, + const char **_orig_name, + struct sss_domain_info **_domain) +{ + enum sss_tool_opt require; + const char *input_name = NULL; + const char *orig_name; + struct sss_domain_info *domain; + errno_t ret; + + *_input_name = NULL; + require = options == NULL ? SSS_TOOL_OPT_OPTIONAL : SSS_TOOL_OPT_REQUIRED; + + ret = sss_tool_popt_ex(cmdline, options, require, + NULL, NULL, "NAME", _("Specify name."), + SSS_TOOL_OPT_REQUIRED, &input_name, NULL); + if (ret != EXIT_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + return ret; + } + + ret = sss_tool_parse_name(tool_ctx, tool_ctx, input_name, + &orig_name, &domain); + if (ret != EOK) { + ERROR("Unable to parse name %s.\n", input_name); + free(discard_const(input_name)); + return ret; + } + + *_input_name = input_name; + *_orig_name = orig_name; + *_domain = domain; + + return EXIT_SUCCESS; +} + +static errno_t parse_cmdline_user_add(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + struct override_user *user) +{ + struct poptOption options[] = { + {"name", 'n', POPT_ARG_STRING, &user->name, 0, _("Override name"), NULL }, + {"uid", 'u', POPT_ARG_INT, &user->uid, 0, _("Override uid (non-zero value)"), NULL }, + {"gid", 'g', POPT_ARG_INT, &user->gid, 0, _("Override gid (non-zero value)"), NULL }, + {"home", 'h', POPT_ARG_STRING, &user->home, 0, _("Override home directory"), NULL }, + {"shell", 's', POPT_ARG_STRING, &user->shell, 0, _("Override shell"), NULL }, + {"gecos", 'c', POPT_ARG_STRING, &user->gecos, 0, _("Override gecos"), NULL }, + {"certificate", 'x', POPT_ARG_STRING, &user->cert, 0, _("Override certificate"), NULL }, + POPT_TABLEEND + }; + + return parse_cmdline(cmdline, tool_ctx, options, &user->input_name, + &user->orig_name, &user->domain); +} + +static errno_t parse_cmdline_user_del(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + struct override_user *user) +{ + return parse_cmdline(cmdline, tool_ctx, NULL, &user->input_name, + &user->orig_name, &user->domain); +} + +static errno_t parse_cmdline_user_show(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + struct override_user *user) +{ + return parse_cmdline(cmdline, tool_ctx, NULL, &user->input_name, + &user->orig_name, &user->domain); +} + +static errno_t parse_cmdline_group_add(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + struct override_group *group) +{ + struct poptOption options[] = { + {"name", 'n', POPT_ARG_STRING, &group->name, 0, _("Override name"), NULL }, + {"gid", 'g', POPT_ARG_INT, &group->gid, 0, _("Override gid"), NULL }, + POPT_TABLEEND + }; + + return parse_cmdline(cmdline, tool_ctx, options, &group->input_name, + &group->orig_name, &group->domain); +} + +static errno_t parse_cmdline_group_del(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + struct override_group *group) +{ + return parse_cmdline(cmdline, tool_ctx, NULL, &group->input_name, + &group->orig_name, &group->domain); +} + +static errno_t parse_cmdline_group_show(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + struct override_group *group) +{ + return parse_cmdline(cmdline, tool_ctx, NULL, &group->input_name, + &group->orig_name, &group->domain); +} + +static errno_t parse_cmdline_find(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + struct sss_domain_info **_dom) +{ + struct sss_domain_info *dom; + const char *domname = NULL; + errno_t ret; + struct poptOption options[] = { + {"domain", 'd', POPT_ARG_STRING | POPT_ARGFLAG_OPTIONAL, + &domname, 0, _("Domain name"), NULL }, + POPT_TABLEEND + }; + + ret = sss_tool_popt_ex(cmdline, options, SSS_TOOL_OPT_OPTIONAL, + NULL, NULL, NULL, NULL, SSS_TOOL_OPT_REQUIRED, + NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + return ret; + } + + if (domname == NULL) { + *_dom = NULL; + return EOK; + } + + dom = find_domain_by_name(tool_ctx->domains, domname, true); + if (dom == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to find domain %s\n", domname); + ERROR("Unable to find domain %s\n", domname); + return EINVAL; + } + + *_dom = dom; + + return EOK; +} + +static errno_t parse_cmdline_import(struct sss_cmdline *cmdline, + const char **_file) +{ + errno_t ret; + + ret = sss_tool_popt_ex(cmdline, NULL, SSS_TOOL_OPT_OPTIONAL, + NULL, NULL, "FILE", "File to import the data from.", + SSS_TOOL_OPT_REQUIRED, _file, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + return ret; + } + + return EOK; +} + +static errno_t parse_cmdline_export(struct sss_cmdline *cmdline, + const char **_file) +{ + errno_t ret; + + ret = sss_tool_popt_ex(cmdline, NULL, SSS_TOOL_OPT_OPTIONAL, + NULL, NULL, "FILE", "File to export the data to.", + SSS_TOOL_OPT_REQUIRED, _file, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + return ret; + } + + return EOK; +} + +static errno_t prepare_view(struct sss_domain_info *domain) +{ + char *viewname = NULL; + errno_t ret; + + ret = sysdb_get_view_name(NULL, domain->sysdb, &viewname); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_view_name() failed.\n"); + return ret; + } + + if (ret == EOK) { + if (is_local_view(viewname)) { + DEBUG(SSSDBG_TRACE_FUNC, "%s view is already present.\n", viewname); + ret = EOK; + goto done; + } else if (viewname != NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "There already exists view %s. " + "Only one view is supported. Nothing to do.\n", viewname); + ret = EEXIST; + goto done; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "Creating %s view.\n", LOCALVIEW); + + ret = sysdb_update_view_name(domain->sysdb, LOCALVIEW); + if (ret == EOK) { + printf("SSSD needs to be restarted for the changes to take effect.\n"); + } + +done: + talloc_free(viewname); + return ret; +} + +errno_t prepare_view_msg(struct sss_domain_info *domain) +{ + errno_t ret; + + ret = prepare_view(domain); + if (ret == EEXIST) { + ERROR("Other than " LOCALVIEW " view already exists " + "in domain %s.\n", domain->name); + } else if (ret != EOK) { + ERROR("Unable to prepare " LOCALVIEW + " view in domain %s.\n", domain->name); + } + + return ret; +} + +static char *build_anchor(TALLOC_CTX *mem_ctx, const char *obj_dn) +{ + char *anchor; + char *safe_dn; + errno_t ret; + + ret = sysdb_dn_sanitize(mem_ctx, obj_dn, &safe_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_dn_sanitize() failed\n"); + return NULL; + } + + anchor = talloc_asprintf(mem_ctx, ":%s:%s", LOCALVIEW, safe_dn); + + talloc_free(safe_dn); + + return anchor; +} + +static struct sysdb_attrs *build_attrs(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + const char *name, + uid_t uid, + gid_t gid, + const char *home, + const char *shell, + const char *gecos, + const char *cert) +{ + struct sysdb_attrs *attrs; + errno_t ret; + char *fqname; + + attrs = sysdb_new_attrs(mem_ctx); + if (attrs == NULL) { + return NULL; + } + + if (name != NULL) { + fqname = sss_create_internal_fqname(attrs, name, dom->name); + if (fqname == NULL) { + return NULL; + } + + ret = sysdb_attrs_add_string(attrs, SYSDB_NAME, fqname); + talloc_free(fqname); + if (ret != EOK) { + goto done; + } + } + + if (uid != 0) { + ret = sysdb_attrs_add_uint32(attrs, SYSDB_UIDNUM, uid); + if (ret != EOK) { + goto done; + } + } + + if (gid != 0) { + ret = sysdb_attrs_add_uint32(attrs, SYSDB_GIDNUM, gid); + if (ret != EOK) { + goto done; + } + } + + if (home != NULL) { + ret = sysdb_attrs_add_string(attrs, SYSDB_HOMEDIR, home); + if (ret != EOK) { + goto done; + } + } + + if (shell != NULL) { + ret = sysdb_attrs_add_string(attrs, SYSDB_SHELL, shell); + if (ret != EOK) { + goto done; + } + } + + if (gecos != NULL) { + ret = sysdb_attrs_add_string(attrs, SYSDB_GECOS, gecos); + if (ret != EOK) { + goto done; + } + } + + if (cert != NULL) { + ret = sysdb_attrs_add_base64_blob(attrs, SYSDB_USER_CERT, cert); + if (ret != EOK) { + goto done; + } + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(attrs); + return NULL; + } + + return attrs; +} + +static struct sysdb_attrs *build_user_attrs(TALLOC_CTX *mem_ctx, + struct override_user *user) +{ + return build_attrs(mem_ctx, user->domain, user->name, user->uid, user->gid, + user->home, user->shell, user->gecos, user->cert); +} + +static struct sysdb_attrs *build_group_attrs(TALLOC_CTX *mem_ctx, + struct override_group *group) +{ + return build_attrs(mem_ctx, group->domain, group->name, 0, group->gid, + 0, NULL, NULL, NULL); +} + +static char *get_fqname(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *name) +{ + char *fqname = NULL; + char *dummy_domain = NULL; + errno_t ret; + TALLOC_CTX *tmp_ctx; + char *shortname; + struct sss_domain_info *dom; + + if (domain == NULL || domain->names == NULL) { + return NULL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return NULL; + } + + /* the name stored in sysdb already contains the lowercased domain */ + ret = sss_parse_internal_fqname(tmp_ctx, name, &shortname, &dummy_domain); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "sss_parse_internal_fqname failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + dom = find_domain_by_name(get_domains_head(domain), dummy_domain, true); + if (dom == NULL) { + goto done; + } + + /* Get length. */ + fqname = sss_tc_fqname(mem_ctx, dom->names, dom, shortname); +done: + talloc_free(tmp_ctx); + return fqname; +} + +static char *get_sysname(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *name) +{ + if (domain == NULL || !domain->fqnames) { + return talloc_strdup(mem_ctx, name); + } + + return sss_tc_fqname(mem_ctx, domain->names, domain, name); +} + +static struct sss_domain_info * +get_object_domain(enum sysdb_member_type type, + const char *name, + struct sss_domain_info *domain, + struct sss_domain_info *domains) +{ + TALLOC_CTX *tmp_ctx; + struct sss_domain_info *dom = NULL; + struct ldb_result *res; + const char *strtype; + char *sysname; + char *fqname = NULL; + bool check_next; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return NULL; + } + + sysname = get_sysname(tmp_ctx, domain, name); + if (sysname == NULL) { + ret = ENOMEM; + goto done; + } + + /* Ensure that the object is in cache. */ + switch (type) { + case SYSDB_MEMBER_USER: + if (getpwnam(sysname) == NULL) { + ret = ENOENT; + goto done; + } + break; + case SYSDB_MEMBER_GROUP: + if (getgrnam(sysname) == NULL) { + ret = ENOENT; + goto done; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported member type %d\n", type); + ret = ERR_INTERNAL; + goto done; + } + + /* Find domain if it is unknown. */ + if (domain == NULL) { + check_next = true; + dom = domains; + } else { + check_next = false; + dom = domain; + } + + do { + talloc_zfree(fqname); + fqname = sss_create_internal_fqname(tmp_ctx, name, dom->name); + if (fqname == NULL) { + ret = ENOMEM; + goto done; + } + + switch (type) { + case SYSDB_MEMBER_USER: + DEBUG(SSSDBG_TRACE_FUNC, "Trying to find user %s@%s\n", + name, dom->name); + ret = sysdb_getpwnam(tmp_ctx, dom, fqname, &res); + strtype = "user"; + break; + case SYSDB_MEMBER_GROUP: + DEBUG(SSSDBG_TRACE_FUNC, "Trying to find group %s@%s\n", + name, dom->name); + ret = sysdb_getgrnam(tmp_ctx, dom, fqname, &res); + strtype = "group"; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported member type %d\n", type); + ret = ERR_INTERNAL; + goto done; + } + + if (ret == EOK && res->count == 0) { + ret = ENOENT; + + if (check_next) { + dom = dom->next; + continue; + } + } + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to find %s %s@%s [%d]: %s\n", + strtype, name, dom->name, ret, sss_strerror(ret)); + goto done; + } else if (res->count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "More than one %s found?\n", strtype); + ret = ERR_INTERNAL; + goto done; + } + + check_next = false; + } while (check_next && dom != NULL); + + if (dom == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No domain match for %s\n", name); + ret = ENOENT; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Domain of %s %s is %s\n", + strtype, name, dom->name); + +done: + talloc_free(tmp_ctx); + + if (ret != EOK) { + return NULL; + } + + return dom; +} + +static errno_t get_user_domain_msg(struct sss_tool_ctx *tool_ctx, + struct override_user *user) +{ + struct sss_domain_info *newdom; + const char *domname; + + newdom = get_object_domain(SYSDB_MEMBER_USER, user->orig_name, + user->domain, tool_ctx->domains); + if (newdom == NULL) { + domname = user->domain == NULL ? "[unknown]" : user->domain->name; + ERROR("Unable to find user %s@%s.\n", user->orig_name, domname); + return ENOENT; + } + + user->sysdb_name = sss_create_internal_fqname(tool_ctx, user->orig_name, + newdom->name); + if (user->sysdb_name == NULL) { + return ENOMEM; + } + + user->domain = newdom; + return EOK; +} + +static errno_t get_group_domain_msg(struct sss_tool_ctx *tool_ctx, + struct override_group *group) +{ + struct sss_domain_info *newdom; + const char *domname; + + newdom = get_object_domain(SYSDB_MEMBER_GROUP, group->orig_name, + group->domain, tool_ctx->domains); + if (newdom == NULL) { + domname = group->domain == NULL ? "[unknown]" : group->domain->name; + ERROR("Unable to find group %s@%s.\n", group->orig_name, domname); + return ENOENT; + } + + group->sysdb_name = sss_create_internal_fqname(tool_ctx, group->orig_name, + newdom->name); + if (group->sysdb_name == NULL) { + return ENOMEM; + } + + group->domain = newdom; + return EOK; +} + +static errno_t get_object_dn(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + enum sysdb_member_type type, + const char *name, + struct ldb_dn **_ldb_dn, + const char **_str_dn) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *ldb_dn; + const char *str_dn; + errno_t ret; + struct ldb_result *res; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + switch (type) { + case SYSDB_MEMBER_USER: + ret = sysdb_getpwnam(tmp_ctx, domain, name, &res); + break; + case SYSDB_MEMBER_GROUP: + ret = sysdb_getgrnam(tmp_ctx, domain, name, &res); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported member type %d\n", type); + ret = ERR_INTERNAL; + goto done; + } + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to look up original object in cache.\n"); + goto done; + } + + if (res->count == 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "Original object not found in cache.\n"); + ret = ENOENT; + goto done; + } else if (res->count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "There are multiple object with name [%s] in the cache.\n", name); + ret = EINVAL; + goto done; + } + + ldb_dn = res->msgs[0]->dn; + + if (ldb_dn == NULL) { + ret = ENOMEM; + goto done; + } + + if (_str_dn != NULL) { + str_dn = talloc_strdup(tmp_ctx, ldb_dn_get_linearized(ldb_dn)); + if (str_dn == NULL) { + ret = ENOMEM; + goto done; + } + + *_str_dn = talloc_steal(mem_ctx, str_dn); + } + + if (_ldb_dn != NULL) { + *_ldb_dn = talloc_steal(mem_ctx, ldb_dn); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t override_object_add(struct sss_domain_info *domain, + enum sysdb_member_type type, + struct sysdb_attrs *attrs, + const char *name) +{ + TALLOC_CTX *tmp_ctx; + const char *anchor; + struct ldb_dn *ldb_dn; + const char *str_dn; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = get_object_dn(tmp_ctx, domain, type, name, &ldb_dn, &str_dn); + if (ret != EOK) { + goto done; + } + + anchor = build_anchor(tmp_ctx, str_dn); + if (anchor == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(attrs, SYSDB_OVERRIDE_ANCHOR_UUID, anchor); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Creating override for %s\n", str_dn); + + ret = sysdb_store_override(domain, LOCALVIEW, type, attrs, ldb_dn); + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t override_fqn(TALLOC_CTX *mem_ctx, + struct sss_tool_ctx *tool_ctx, + struct sss_domain_info *domain, + const char *input, + const char **_name) +{ + struct sss_domain_info *dom; + errno_t ret; + + if (input == NULL) { + return EOK; + } + + ret = sss_tool_parse_name(mem_ctx, tool_ctx, input, _name, &dom); + if (ret == EAGAIN) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to find domain from " + "fqn %s\n", input); + ERROR("Changing domain is not allowed!\n"); + ret = EINVAL; + } else if (ret == EOK && dom != NULL && dom != domain) { + DEBUG(SSSDBG_OP_FAILURE, "Trying to change domain from " + "%s to %s, not allowed!\n", domain->name, dom->name); + ERROR("Changing domain is not allowed!\n"); + ret = EINVAL; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse name %s [%d]: %s\n", + input, ret, sss_strerror(ret)); + } + + return ret; +} + +static errno_t override_user(struct sss_tool_ctx *tool_ctx, + struct override_user *input_user) +{ + TALLOC_CTX *tmp_ctx; + struct override_user user; + struct sysdb_attrs *attrs; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + user = *input_user; + + /* We need to parse the name and ensure that domain did not change. */ + ret = override_fqn(tmp_ctx, tool_ctx, user.domain, user.name, &user.name); + if (ret != EOK) { + goto done; + } + + ret = prepare_view_msg(user.domain); + if (ret != EOK) { + goto done; + } + + attrs = build_user_attrs(tool_ctx, &user); + if (attrs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build sysdb attrs.\n"); + ret = ENOMEM; + goto done; + } + + ret = override_object_add(user.domain, SYSDB_MEMBER_USER, attrs, + user.sysdb_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add override object.\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t override_group(struct sss_tool_ctx *tool_ctx, + struct override_group *input_group) +{ + TALLOC_CTX *tmp_ctx; + struct override_group group; + struct sysdb_attrs *attrs; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + group = *input_group; + + /* We need to parse the name and ensure that domain did not change. */ + ret = override_fqn(tmp_ctx, tool_ctx, group.domain, group.name, + &group.name); + if (ret != EOK) { + goto done; + } + + ret = prepare_view_msg(group.domain); + if (ret != EOK) { + goto done; + } + + attrs = build_group_attrs(tool_ctx, &group); + if (attrs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build sysdb attrs.\n"); + ret = ENOMEM; + goto done; + } + + ret = override_object_add(group.domain, SYSDB_MEMBER_GROUP, attrs, + group.sysdb_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add override object.\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t override_object_del(struct sss_domain_info *domain, + enum sysdb_member_type type, + const char *name) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message *msg; + struct ldb_dn *override_dn; + struct ldb_dn *ldb_dn; + const char *str_dn; + const char *anchor; + errno_t ret; + int sret; + bool in_transaction = false; + struct ldb_context *ldb = sysdb_ctx_get_ldb(domain->sysdb); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = get_object_dn(tmp_ctx, domain, type, name, &ldb_dn, &str_dn); + if (ret != EOK) { + goto done; + } + + anchor = build_anchor(tmp_ctx, str_dn); + if (anchor == NULL) { + ret = ENOMEM; + goto done; + } + + override_dn = ldb_dn_new_fmt(tmp_ctx, ldb, + SYSDB_TMPL_OVERRIDE, anchor, LOCALVIEW); + if (override_dn == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Removing override for %s\n", str_dn); + + ret = sysdb_transaction_start(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_transaction_start() failed.\n"); + goto done; + } + in_transaction = true; + + ret = sysdb_delete_entry(domain->sysdb, override_dn, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_delete_entry() failed.\n"); + goto done; + } + + msg = ldb_msg_new(tmp_ctx); + if (msg == NULL) { + ret = ENOMEM; + goto done; + } + + msg->dn = talloc_steal(msg, ldb_dn); + if (msg->dn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = ldb_msg_add_empty(msg, SYSDB_OVERRIDE_DN, LDB_FLAG_MOD_DELETE, NULL); + if (ret != LDB_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_msg_add_empty() failed\n"); + ret = sss_ldb_error_to_errno(ret); + goto done; + } + + ret = ldb_modify(ldb, msg); + if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_ATTRIBUTE) { + DEBUG(SSSDBG_OP_FAILURE, + "ldb_modify() failed: [%s](%d)[%s]\n", + ldb_strerror(ret), ret, ldb_errstring(ldb)); + ret = sss_ldb_error_to_errno(ret); + goto done; + } + + ret = sysdb_transaction_commit(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + ret = EOK; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(domain->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + + talloc_free(tmp_ctx); + return ret; +} + +static errno_t append_name(struct sss_domain_info *domain, + struct ldb_message *override) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_context *ldb = sysdb_ctx_get_ldb(domain->sysdb); + struct ldb_dn *dn; + struct ldb_message **msgs; + const char *attrs[] = {SYSDB_NAME, NULL}; + const char *name; + const char *fqname; + size_t count; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, override, + SYSDB_OVERRIDE_OBJECT_DN); + if (dn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing overrideObjectDN?\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = sysdb_search_entry(tmp_ctx, domain->sysdb, dn, LDB_SCOPE_BASE, + NULL, attrs, &count, &msgs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_search_entry() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } else if (count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "More than one user found?\n"); + ret = ERR_INTERNAL; + goto done; + } + + name = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Object with no name?\n"); + ret = ERR_INTERNAL; + goto done; + } + + fqname = get_fqname(tmp_ctx, domain, name); + if (fqname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get fqname\n"); + ret = ENOMEM; + goto done; + } + + ret = ldb_msg_add_string(override, ORIGNAME, fqname); + if (ret != LDB_SUCCESS) { + ret = sss_ldb_error_to_errno(ret); + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add attribute to msg\n"); + goto done; + } + + talloc_steal(override, fqname); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t list_overrides(TALLOC_CTX *mem_ctx, + const char *base_filter, + const char *ext_filter, + const char **attrs, + struct sss_domain_info *domain, + size_t *_count, + struct ldb_message ***_msgs) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *dn; + struct ldb_context *ldb = sysdb_ctx_get_ldb(domain->sysdb); + size_t count; + struct ldb_message **msgs; + const char *filter; + size_t i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + filter = base_filter; + if (ext_filter != NULL) { + filter = talloc_asprintf(tmp_ctx, "(&%s%s)", filter, ext_filter); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed.\n"); + ret = ENOMEM; + goto done; + } + } + + /* Acquire list of override objects. */ + dn = ldb_dn_new_fmt(tmp_ctx, ldb, SYSDB_TMPL_VIEW_SEARCH_BASE, LOCALVIEW); + if (dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_new_fmt() failed.\n"); + ret = EIO; + goto done; + } + + ret = sysdb_search_entry(tmp_ctx, domain->sysdb, dn, LDB_SCOPE_SUBTREE, + filter, attrs, &count, &msgs); + if (ret == ENOENT) { + *_msgs = NULL; + *_count = 0; + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_search_entry() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Amend messages with original name. */ + for (i = 0; i < count; i++) { + ret = append_name(domain, msgs[i]); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to append name [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + *_msgs = talloc_steal(mem_ctx, msgs); + *_count = count; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static struct override_user * +list_user_overrides(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *filter) +{ + TALLOC_CTX *tmp_ctx; + struct override_user *objs = NULL; + struct ldb_message **msgs; + size_t count; + size_t i; + errno_t ret; + const char *attrs[] = SYSDB_PW_ATTRS; + struct ldb_message_element *el; + const char *fqname; + char *name; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed.\n"); + return NULL; + } + + ret = list_overrides(tmp_ctx, "(objectClass=" SYSDB_OVERRIDE_USER_CLASS ")", + filter, attrs, domain, &count, &msgs); + if (ret != EOK) { + goto done; + } + + objs = talloc_zero_array(tmp_ctx, struct override_user, count + 1); + if (objs == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < count; i++) { + objs[i].orig_name = ldb_msg_find_attr_as_string(msgs[i], ORIGNAME, + NULL); + if (objs[i].orig_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing name?!\n"); + ret = ERR_INTERNAL; + goto done; + } + + fqname = ldb_msg_find_attr_as_string(msgs[i], SYSDB_NAME, NULL); + if (fqname != NULL) { + ret = sss_parse_internal_fqname(tmp_ctx, fqname, &name, NULL); + if (ret != EOK) { + ret = ERR_WRONG_NAME_FORMAT; + goto done; + } + objs[i].name = talloc_steal(objs, name); + } + + objs[i].uid = ldb_msg_find_attr_as_uint(msgs[i], SYSDB_UIDNUM, 0); + objs[i].gid = ldb_msg_find_attr_as_uint(msgs[i], SYSDB_GIDNUM, 0); + objs[i].home = ldb_msg_find_attr_as_string(msgs[i], SYSDB_HOMEDIR, NULL); + objs[i].shell = ldb_msg_find_attr_as_string(msgs[i], SYSDB_SHELL, NULL); + objs[i].gecos = ldb_msg_find_attr_as_string(msgs[i], SYSDB_GECOS, NULL); + + el = ldb_msg_find_element(msgs[i], SYSDB_USER_CERT); + if (el != NULL && el->num_values > 0) { + /* Currently we support only 1 certificate override */ + objs[i].cert = sss_base64_encode(objs, el->values[0].data, + el->values[0].length); + if (objs[i].cert == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_base64_encode failed.\n"); + ret = ERR_INTERNAL; + goto done; + } + } else { + objs[i].cert = NULL; + } + + talloc_steal(objs, objs[i].orig_name); + talloc_steal(objs, objs[i].home); + talloc_steal(objs, objs[i].shell); + talloc_steal(objs, objs[i].gecos); + } + + talloc_steal(mem_ctx, objs); + +done: + talloc_free(tmp_ctx); + + if (ret != EOK) { + return NULL; + } + + return objs; +} + +static struct override_group * +list_group_overrides(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *filter) +{ + TALLOC_CTX *tmp_ctx; + struct override_group *objs = NULL; + struct ldb_message **msgs; + size_t count; + size_t i; + errno_t ret; + const char *attrs[] = SYSDB_GRSRC_ATTRS; + const char *fqname; + char *name; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed.\n"); + return NULL; + } + + ret = list_overrides(tmp_ctx, "(objectClass=" SYSDB_OVERRIDE_GROUP_CLASS ")", + filter, attrs, domain, &count, &msgs); + if (ret != EOK) { + goto done; + } + + objs = talloc_zero_array(tmp_ctx, struct override_group, count + 1); + if (objs == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < count; i++) { + objs[i].orig_name = ldb_msg_find_attr_as_string(msgs[i], ORIGNAME, + NULL); + if (objs[i].orig_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing name?!\n"); + ret = ERR_INTERNAL; + goto done; + } + talloc_steal(objs, objs[i].orig_name); + + fqname = ldb_msg_find_attr_as_string(msgs[i], SYSDB_NAME, NULL); + if (fqname != NULL) { + ret = sss_parse_internal_fqname(tmp_ctx, fqname, &name, NULL); + if (ret != EOK) { + ret = ERR_WRONG_NAME_FORMAT; + goto done; + } + objs[i].name = talloc_steal(objs, name); + } + + objs[i].gid = ldb_msg_find_attr_as_uint(msgs[i], SYSDB_GIDNUM, 0); + } + + talloc_steal(mem_ctx, objs); + +done: + talloc_free(tmp_ctx); + + if (ret != EOK) { + return NULL; + } + + return objs; +} + +static errno_t user_export(const char *filename, + struct sss_domain_info *dom, + bool iterate, + const char *filter) +{ + TALLOC_CTX *tmp_ctx; + struct sss_colondb *db; + struct override_user *objs; + errno_t ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + db = sss_colondb_open(tmp_ctx, SSS_COLONDB_WRITE, filename); + if (db == NULL) { + ERROR("Unable to open %s.\n", + filename == NULL ? "stdout" : filename); + ret = EIO; + goto done; + } + + do { + objs = list_user_overrides(tmp_ctx, dom, filter); + if (objs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get override objects\n"); + ret = ENOMEM; + goto done; + } + + for (i = 0; objs[i].orig_name != NULL; i++) { + /** + * Format: orig_name:name:uid:gid:gecos:home:shell:certificate + */ + struct sss_colondb_write_field table[] = { + {SSS_COLONDB_STRING, {.str = objs[i].orig_name}}, + {SSS_COLONDB_STRING, {.str = objs[i].name}}, + {SSS_COLONDB_UINT32, {.uint32 = objs[i].uid}}, + {SSS_COLONDB_UINT32, {.uint32 = objs[i].gid}}, + {SSS_COLONDB_STRING, {.str = objs[i].gecos}}, + {SSS_COLONDB_STRING, {.str = objs[i].home}}, + {SSS_COLONDB_STRING, {.str = objs[i].shell}}, + {SSS_COLONDB_STRING, {.str = objs[i].cert}}, + {SSS_COLONDB_SENTINEL, {0}} + }; + + ret = sss_colondb_writeline(db, table); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to write line to db\n"); + goto done; + } + } + + /* All overrides are under the same subtree, so we don't want to + * descent into subdomains. */ + dom = get_next_domain(dom, false); + } while (dom != NULL && iterate); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t group_export(const char *filename, + struct sss_domain_info *dom, + bool iterate, + const char *filter) +{ + TALLOC_CTX *tmp_ctx; + struct sss_colondb *db; + struct override_group *objs; + errno_t ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + + db = sss_colondb_open(tmp_ctx, SSS_COLONDB_WRITE, filename); + if (db == NULL) { + ERROR("Unable to open %s.\n", + filename == NULL ? "stdout" : filename); + ret = EIO; + goto done; + } + + do { + objs = list_group_overrides(tmp_ctx, dom, filter); + if (objs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get override objects\n"); + ret = ENOMEM; + goto done; + } + + for (i = 0; objs[i].orig_name != NULL; i++) { + /** + * Format: orig_name:name:gid + */ + struct sss_colondb_write_field table[] = { + {SSS_COLONDB_STRING, {.str = objs[i].orig_name}}, + {SSS_COLONDB_STRING, {.str = objs[i].name}}, + {SSS_COLONDB_UINT32, {.uint32 = objs[i].gid}}, + {SSS_COLONDB_SENTINEL, {0}} + }; + + ret = sss_colondb_writeline(db, table); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to write line to db\n"); + goto done; + } + } + + /* All overrides are under the same subtree, so we don't want to + * descent into subdomains. */ + dom = get_next_domain(dom, false); + } while (dom != NULL && iterate); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static int override_user_add(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + struct override_user user = {NULL}; + errno_t ret; + + ret = parse_cmdline_user_add(cmdline, tool_ctx, &user); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command line.\n"); + goto done; + } + + ret = get_user_domain_msg(tool_ctx, &user); + if (ret != EOK) { + goto done; + } + + ret = override_user(tool_ctx, &user); + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + free(discard_const(user.input_name)); + + return ret; +} + +static int override_user_del(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + struct override_user user = {NULL}; + errno_t ret; + + ret = parse_cmdline_user_del(cmdline, tool_ctx, &user); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command line.\n"); + goto done; + } + + ret = get_user_domain_msg(tool_ctx, &user); + if (ret != EOK) { + goto done; + } + + ret = override_object_del(user.domain, SYSDB_MEMBER_USER, user.sysdb_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to delete override object.\n"); + goto done; + } + + ret = EOK; + +done: + free(discard_const(user.input_name)); + + return ret; +} + +static int override_user_find(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + struct sss_domain_info *dom; + bool iterate; + errno_t ret; + + ret = parse_cmdline_find(cmdline, tool_ctx, &dom); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command line.\n"); + return ret; + } + + if (dom == NULL) { + dom = tool_ctx->domains; + iterate = true; + } else { + iterate = false; + } + + ret = user_export(NULL, dom, iterate, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to export users\n"); + return ret; + } + + return EOK; +} + +static int override_user_show(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + TALLOC_CTX *tmp_ctx; + struct override_user input = {NULL}; + const char *dn; + char *anchor; + const char *filter; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + ret = parse_cmdline_user_show(cmdline, tool_ctx, &input); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command line.\n"); + goto done; + } + + ret = get_user_domain_msg(tool_ctx, &input); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get object domain\n"); + goto done; + } + + ret = get_object_dn(tmp_ctx, input.domain, SYSDB_MEMBER_USER, + input.sysdb_name, NULL, &dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get object dn\n"); + goto done; + } + + anchor = build_anchor(tmp_ctx, dn); + if (anchor == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_filter_sanitize(tmp_ctx, anchor, &anchor); + if (ret != EOK) { + ret = ENOMEM; + goto done; + } + + filter = talloc_asprintf(tmp_ctx, "(%s=%s)", + SYSDB_OVERRIDE_ANCHOR_UUID, anchor); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + ret = ENOMEM; + goto done; + } + + ret = user_export(NULL, input.domain, false, filter); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to export users\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + free(discard_const(input.input_name)); + + return ret; +} + +static int override_user_import(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + TALLOC_CTX *tmp_ctx; + struct sss_colondb *db; + const char *filename = NULL; + struct override_user obj = {0}; + int linenum = 1; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed.\n"); + return EXIT_FAILURE; + } + + /** + * Format: orig_name:name:uid:gid:gecos:home:shell:certificate + */ + struct sss_colondb_read_field table[] = { + {SSS_COLONDB_STRING, {.str = &obj.input_name}}, + {SSS_COLONDB_STRING, {.str = &obj.name}}, + {SSS_COLONDB_UINT32, {.uint32 = &obj.uid}}, + {SSS_COLONDB_UINT32, {.uint32 = &obj.gid}}, + {SSS_COLONDB_STRING, {.str = &obj.gecos}}, + {SSS_COLONDB_STRING, {.str = &obj.home}}, + {SSS_COLONDB_STRING, {.str = &obj.shell}}, + {SSS_COLONDB_STRING, {.str = &obj.cert}}, + {SSS_COLONDB_SENTINEL, {0}} + }; + + ret = parse_cmdline_import(cmdline, &filename); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command line.\n"); + goto done; + } + + db = sss_colondb_open(tool_ctx, SSS_COLONDB_READ, filename); + if (db == NULL) { + ERROR("Unable to open %s.\n", filename); + ret = EIO; + goto done; + } + + while ((ret = sss_colondb_readline(tmp_ctx, db, table)) == EOK) { + linenum++; + + ret = sss_tool_parse_name(tool_ctx, tool_ctx, obj.input_name, + &obj.orig_name, &obj.domain); + if (ret != EOK) { + ERROR("Unable to parse name %s.\n", obj.input_name); + goto done; + } + + ret = get_user_domain_msg(tool_ctx, &obj); + if (ret != EOK) { + goto done; + } + + ret = override_user(tool_ctx, &obj); + if (ret != EOK) { + goto done; + } + + talloc_free_children(tmp_ctx); + } + + if (ret != EOF) { + ERROR("Invalid format on line %d. " + "Use --debug option for more information.\n", linenum); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + free(discard_const(filename)); + + return ret; +} + +static int override_user_export(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + const char *filename = NULL; + errno_t ret; + + ret = parse_cmdline_export(cmdline, &filename); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command line.\n"); + goto done; + } + + ret = user_export(filename, tool_ctx->domains, true, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to export users\n"); + goto done; + } + + ret = EOK; + +done: + free(discard_const(filename)); + + return ret; +} + +static int override_group_add(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + struct override_group group = {NULL}; + errno_t ret; + + ret = parse_cmdline_group_add(cmdline, tool_ctx, &group); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command line.\n"); + goto done; + } + + ret = get_group_domain_msg(tool_ctx, &group); + if (ret != EOK) { + goto done; + } + + ret = override_group(tool_ctx, &group); + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + free(discard_const(group.input_name)); + + return ret; +} + +static int override_group_del(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + struct override_group group = {NULL}; + errno_t ret; + + ret = parse_cmdline_group_del(cmdline, tool_ctx, &group); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command line.\n"); + goto done; + } + + ret = get_group_domain_msg(tool_ctx, &group); + if (ret != EOK) { + goto done; + } + + ret = override_object_del(group.domain, SYSDB_MEMBER_GROUP, + group.sysdb_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to delete override object.\n"); + goto done; + } + + ret = EOK; + +done: + free(discard_const(group.input_name)); + + return ret; +} + +static int override_group_find(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + struct sss_domain_info *dom; + bool iterate; + errno_t ret; + + ret = parse_cmdline_find(cmdline, tool_ctx, &dom); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command line.\n"); + return ret; + } + + if (dom == NULL) { + dom = tool_ctx->domains; + iterate = true; + } else { + iterate = false; + } + + ret = group_export(NULL, dom, iterate, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to export groups\n"); + return ret; + } + + return EOK; +} + +static int override_group_show(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + TALLOC_CTX *tmp_ctx; + struct override_group input = {NULL}; + const char *dn; + char *anchor; + const char *filter; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + ret = parse_cmdline_group_show(cmdline, tool_ctx, &input); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command line.\n"); + goto done; + } + + ret = get_group_domain_msg(tool_ctx, &input); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get object domain\n"); + goto done; + } + + ret = get_object_dn(tmp_ctx, input.domain, SYSDB_MEMBER_GROUP, + input.sysdb_name, NULL, &dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get object dn\n"); + goto done; + } + + anchor = build_anchor(tmp_ctx, dn); + if (anchor == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_filter_sanitize(tmp_ctx, anchor, &anchor); + if (ret != EOK) { + ret = ENOMEM; + goto done; + } + + filter = talloc_asprintf(tmp_ctx, "(%s=%s)", + SYSDB_OVERRIDE_ANCHOR_UUID, anchor); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + ret = ENOMEM; + goto done; + } + + ret = group_export(NULL, input.domain, false, filter); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to export groups\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + free(discard_const(input.input_name)); + + return ret; +} + +static int override_group_import(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + TALLOC_CTX *tmp_ctx; + struct sss_colondb *db; + const char *filename = NULL; + struct override_group obj = {0}; + int linenum = 1; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + /** + * Format: orig_name:name:gid + */ + struct sss_colondb_read_field table[] = { + {SSS_COLONDB_STRING, {.str = &obj.input_name}}, + {SSS_COLONDB_STRING, {.str = &obj.name}}, + {SSS_COLONDB_UINT32, {.uint32 = &obj.gid}}, + {SSS_COLONDB_SENTINEL, {0}} + }; + + ret = parse_cmdline_import(cmdline, &filename); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command line.\n"); + goto done; + } + + db = sss_colondb_open(tool_ctx, SSS_COLONDB_READ, filename); + if (db == NULL) { + ERROR("Unable to open %s.\n", filename); + ret = EIO; + goto done; + } + + while ((ret = sss_colondb_readline(tmp_ctx, db, table)) == EOK) { + linenum++; + + ret = sss_tool_parse_name(tool_ctx, tool_ctx, obj.input_name, + &obj.orig_name, &obj.domain); + if (ret != EOK) { + ERROR("Unable to parse name %s.\n", obj.input_name); + goto done; + } + + ret = get_group_domain_msg(tool_ctx, &obj); + if (ret != EOK) { + goto done; + } + + ret = override_group(tool_ctx, &obj); + if (ret != EOK) { + goto done; + } + + talloc_free_children(tmp_ctx); + } + + if (ret != EOF) { + ERROR("Invalid format on line %d. " + "Use --debug option for more information.\n", linenum); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + free(discard_const(filename)); + + return ret; +} + +static int override_group_export(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + const char *filename = NULL; + errno_t ret; + + ret = parse_cmdline_export(cmdline, &filename); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command line.\n"); + goto done; + } + + ret = group_export(filename, tool_ctx->domains, true, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to export groups\n"); + goto done; + } + + ret = EOK; + +done: + free(discard_const(filename)); + + return ret; +} + +int main(int argc, const char **argv) +{ + struct sss_route_cmd commands[] = { + SSS_TOOL_COMMAND_NOMSG("user-add", 0, override_user_add), + SSS_TOOL_COMMAND_NOMSG("user-del", 0, override_user_del), + SSS_TOOL_COMMAND_NOMSG("user-find", 0, override_user_find), + SSS_TOOL_COMMAND_NOMSG("user-show", 0, override_user_show), + SSS_TOOL_COMMAND_NOMSG("user-import", 0, override_user_import), + SSS_TOOL_COMMAND_NOMSG("user-export", 0, override_user_export), + SSS_TOOL_COMMAND_NOMSG("group-add", 0, override_group_add), + SSS_TOOL_COMMAND_NOMSG("group-del", 0, override_group_del), + SSS_TOOL_COMMAND_NOMSG("group-find", 0, override_group_find), + SSS_TOOL_COMMAND_NOMSG("group-show", 0, override_group_show), + SSS_TOOL_COMMAND_NOMSG("group-import", 0, override_group_import), + SSS_TOOL_COMMAND_NOMSG("group-export", 0, override_group_export), + SSS_TOOL_LAST + }; + + return sss_tool_main(argc, argv, commands, NULL); +} diff --git a/src/tools/sss_seed.c b/src/tools/sss_seed.c new file mode 100644 index 0000000..07327da --- /dev/null +++ b/src/tools/sss_seed.c @@ -0,0 +1,896 @@ +/* + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <talloc.h> +#include <popt.h> +#include <errno.h> +#include <unistd.h> +#include <limits.h> +#include <grp.h> +#include <pwd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <ctype.h> + +#include "util/util.h" +#include "db/sysdb.h" +#include "tools/tools_util.h" +#include "confdb/confdb.h" + +#ifndef BUFSIZE +#define BUFSIZE 1024 +#endif + +#ifndef PASS_MAX +#define PASS_MAX 64 +#endif + +enum seed_pass_method { + PASS_PROMPT, + PASS_FILE +}; + +struct user_ctx { + char *domain_name; + + char *name; + uid_t uid; + gid_t gid; + char *gecos; + char *home; + char *shell; + + char *password; +}; + +struct seed_ctx { + struct confdb_ctx *confdb; + struct sss_domain_info *domain; + struct sysdb_ctx *sysdb; + + struct user_ctx *uctx; + + char *password_file; + enum seed_pass_method password_method; + + bool interact; + bool user_cached; +}; + + +static int seed_prompt(const char *req) +{ + ssize_t len = 0; + size_t i = 0; + char *prompt = NULL; + int ret = EOK; + + prompt = talloc_asprintf(NULL, _("Enter %s:"), req); + if (prompt == NULL) { + ret = ENOMEM; + goto done; + } + + while (prompt[i] != '\0') { + errno = 0; + len = sss_atomic_write_s(STDOUT_FILENO, &prompt[i++], 1); + if (len == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "write failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + } + +done: + talloc_free(prompt); + return ret; +} + +static int seed_str_input(TALLOC_CTX *mem_ctx, + const char *req, + char **_input) +{ + char buf[BUFSIZE+1]; + size_t len = 0; + size_t bytes_read = 0; + int ret = EOK; + + ret = seed_prompt(req); + if (ret != EOK) { + return ret; + } + + errno = 0; + while ((bytes_read = sss_atomic_read_s(STDIN_FILENO, buf+len, 1)) != 0) { + if (bytes_read == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "read failed [%d][%s].\n", + ret, strerror(ret)); + return ret; + } + if (buf[len] == '\n' || len == BUFSIZE) { + buf[len] = '\0'; + break; + } + len += bytes_read; + } + + *_input = talloc_strdup(mem_ctx, buf); + if (*_input == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate input\n"); + } + + return ret; +} + +static int seed_id_input(const char *req, + uid_t *_id_input) +{ + char buf[BUFSIZE+1]; + size_t len = 0; + size_t bytes_read = 0; + char *endptr = NULL; + int ret = EOK; + + ret = seed_prompt(req); + if (ret != EOK) { + return ret; + } + + errno = 0; + while ((bytes_read = sss_atomic_read_s(STDIN_FILENO, buf+len, 1)) != 0) { + if (bytes_read == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "read failed [%d][%s].\n", + ret, strerror(ret)); + return ret; + } + if (buf[len] == '\n' || len == BUFSIZE) { + buf[len] = '\0'; + break; + } + len += bytes_read; + } + + if (isdigit(*buf)) { + errno = 0; + *_id_input = (uid_t)strtoll(buf, &endptr, 10); + if (errno != 0) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, "strtoll failed on [%s]: [%d][%s].\n", + (char *)buf, ret, strerror(ret)); + return ret; + } + if (*endptr != '\0') { + DEBUG(SSSDBG_MINOR_FAILURE, + "extra characters [%s] after ID [%"SPRIuid"]\n", + endptr, *_id_input); + } + } else { + ret = EINVAL; + DEBUG(SSSDBG_OP_FAILURE, "Failed to get %s input.\n", req); + } + + return ret; +} + +static int seed_password_input_prompt(TALLOC_CTX *mem_ctx, char **_password) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *password = NULL; + char *temp = NULL; + int ret = EOK; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not allocate temp context\n"); + ret = ENOMEM; + goto done; + } + + temp = getpass("Enter temporary password:"); + if (temp == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get prompted password\n"); + ret = EINVAL; + goto done; + } + + /* Do not allow empty passwords */ + if (strlen(temp) == 0) { + ERROR("Empty passwords are not allowed.\n"); + ret = EINVAL; + goto done; + } + + password = talloc_strdup(tmp_ctx, temp); + sss_erase_mem_securely(temp, strlen(temp)); + if (password == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_set_destructor((TALLOC_CTX *)password, + sss_erase_talloc_mem_securely); + + temp = getpass("Enter temporary password again:"); + if (temp == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get prompted password\n"); + ret = EINVAL; + goto done; + } + + if (strncmp(temp,password,strlen(password)) != 0) { + ERROR("Passwords do not match\n"); + DEBUG(SSSDBG_MINOR_FAILURE, "Provided passwords do not match\n"); + ret = EINVAL; + goto done; + } + + *_password = talloc_steal(mem_ctx, password); + +done: + talloc_free(tmp_ctx); + if (temp != NULL) { + sss_erase_mem_securely(temp, strlen(temp)); + } + return ret; +} + +static int seed_password_input_file(TALLOC_CTX *mem_ctx, + char *filename, + char **_password) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *password = NULL; + int len = 0; + uint8_t buf[PASS_MAX+1]; + int fd = -1; + int ret = EOK; + int valid_i; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not allocate temp context\n"); + ret = ENOMEM; + goto done; + } + + fd = open(filename, O_RDONLY); + if (fd == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to open password file " + "[%s] [%d][%s]\n", + filename, errno, strerror(errno)); + ret = EINVAL; + goto done; + } + + errno = 0; + len = sss_atomic_read_s(fd, buf, PASS_MAX + 1); + if (len == -1) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to read password from file " + "[%s] [%d][%s]\n", + filename, ret, strerror(ret)); + close(fd); + goto done; + } + + close(fd); + + if (len > PASS_MAX) { + ERROR("Password file too big.\n"); + ret = EINVAL; + goto done; + } + + buf[len] = '\0'; + + /* Only the first line is valid (without '\n'). */ + for (valid_i = -1; valid_i + 1 < len; valid_i++) { + if (buf[valid_i + 1] == '\n') { + buf[valid_i + 1] = '\0'; + break; + } + } + + /* Do not allow empty passwords. */ + if (valid_i < 0) { + ERROR("Empty passwords are not allowed.\n"); + ret = EINVAL; + goto done; + } + + /* valid_i is the last valid index of the password followed by \0. + * If characters other than \n occur int the rest of the file, it + * is an error. */ + for (i = valid_i + 2; i < len; i++) { + if (buf[i] != '\n') { + ERROR("Multi-line passwords are not allowed.\n"); + ret = EINVAL; + goto done; + } + } + + password = talloc_strdup(tmp_ctx, (char *)buf); + if (password == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_set_destructor((TALLOC_CTX *)password, + sss_erase_talloc_mem_securely); + + *_password = talloc_steal(mem_ctx, password); + +done: + talloc_free(tmp_ctx); + sss_erase_mem_securely(buf, sizeof(buf)); + return ret; +} + +static int seed_interactive_input(TALLOC_CTX *mem_ctx, + struct user_ctx *uctx, + struct user_ctx **_uctx) +{ + struct user_ctx *input_uctx = NULL; + int ret = EOK; + + input_uctx = talloc_zero(NULL, struct user_ctx); + if (input_uctx == NULL) { + ret = ENOMEM; + goto done; + } + + if (uctx->name == NULL) { + ret = seed_str_input(input_uctx, _("username"), &input_uctx->name); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Username interactive input failed.\n"); + goto done; + } + } else { + input_uctx->name = talloc_strdup(input_uctx, uctx->name); + if (input_uctx->name == NULL) { + ret = ENOMEM; + goto done; + } + } + + if (uctx->uid == 0) { + ret = seed_id_input(_("UID"), &input_uctx->uid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "UID interactive input failed.\n"); + goto done; + } + } else { + input_uctx->uid = uctx->uid; + } + + if (uctx->gid == 0) { + ret = seed_id_input(_("GID"), &input_uctx->gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "GID interactive input failed.\n"); + goto done; + } + } else { + input_uctx->gid = uctx->gid; + } + + if (uctx->gecos == NULL) { + ret = seed_str_input(input_uctx, _("user comment (gecos)"), + &input_uctx->gecos); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Gecos interactive input failed.\n"); + goto done; + } + } else { + input_uctx->gecos = talloc_strdup(input_uctx, uctx->gecos); + if (input_uctx->gecos == NULL) { + ret = ENOMEM; + goto done; + } + } + + if (uctx->home == NULL) { + ret = seed_str_input(input_uctx, _("home directory"), + &input_uctx->home); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Home directory interactive input fialed.\n"); + goto done; + } + } else { + input_uctx->home = talloc_strdup(input_uctx, uctx->home); + if (input_uctx->home == NULL) { + ret = ENOMEM; + goto done; + } + } + + if (uctx->shell == NULL) { + ret = seed_str_input(input_uctx, _("user login shell"), + &input_uctx->shell); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Shell interactive input failed\n"); + goto done; + } + } else { + input_uctx->shell = talloc_strdup(input_uctx, uctx->shell); + if (input_uctx->shell == NULL) { + ret = ENOMEM; + goto done; + } + } + +done: + if (ret == EOK) { + *_uctx = talloc_steal(mem_ctx, input_uctx); + } else { + ERROR("Interactive input failed.\n"); + talloc_zfree(input_uctx); + } + return ret; +} + +static int seed_init(TALLOC_CTX *mem_ctx, + const int argc, + const char **argv, + struct seed_ctx **_sctx) +{ + TALLOC_CTX *tmp_ctx = NULL; + int pc_debug = SSSDBG_TOOLS_DEFAULT; + const char *pc_domain = NULL; + const char *pc_name = NULL; + uid_t pc_uid = 0; + gid_t pc_gid = 0; + const char *pc_gecos = NULL; + const char *pc_home = NULL; + const char *pc_shell = NULL; + const char *pc_password_file = NULL; + + struct seed_ctx *sctx = NULL; + + int ret = EOK; + + poptContext pc = NULL; + struct poptOption options[] = { + POPT_AUTOHELP + { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug, 0, + _("The debug level to run with"), NULL }, + { "domain", 'D', POPT_ARG_STRING, &pc_domain, 0, _("Domain"), NULL }, + { "username", 'n', POPT_ARG_STRING, &pc_name, 0, _("Username"), NULL}, + { "uid", 'u', POPT_ARG_INT, &pc_uid, 0, _("User UID"), NULL }, + { "gid", 'g', POPT_ARG_INT, &pc_gid, 0, _("User GID"), NULL }, + { "gecos", 'c', POPT_ARG_STRING, &pc_gecos, 0, + _("Comment string"), NULL}, + { "home", 'h', POPT_ARG_STRING, &pc_home, 0, + _("Home directory"), NULL }, + { "shell", 's', POPT_ARG_STRING, &pc_shell, 0, _("Login Shell"), NULL }, + { "interactive", 'i', POPT_ARG_NONE, NULL, 'i', + _("Use interactive mode to enter user data"), NULL }, + { "password-file", 'p', POPT_ARG_STRING, &pc_password_file, 0, + _("File from which user's password is read " + "(default is to prompt for password)"),NULL }, + POPT_TABLEEND + }; + + /* init contexts */ + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto fini; + } + + sctx = talloc_zero(tmp_ctx, struct seed_ctx); + if (sctx == NULL) { + ERROR("Could not allocate tools context\n"); + ret = ENOMEM; + goto fini; + } + + sctx->uctx = talloc_zero(sctx, struct user_ctx); + if (sctx->uctx == NULL) { + ERROR("Could not allocate user data context\n"); + ret = ENOMEM; + goto fini; + } + + debug_prg_name = argv[0]; + ret = set_locale(); + if (ret != EOK) { + ERROR("set_locale failed (%d): %s\n", ret, strerror(ret)); + ret = EINVAL; + goto fini; + } + + /* parse arguments */ + pc = poptGetContext(NULL, argc, argv, options, 0); + if (argc < 2) { + poptPrintUsage(pc,stderr,0); + ret = EINVAL; + goto fini; + } + + poptSetOtherOptionHelp(pc, "[OPTIONS] -D <domain> -n <username>"); + while ((ret = poptGetNextOpt(pc)) > 0) { + switch (ret) { + case 'i': + DEBUG(SSSDBG_TRACE_INTERNAL, "Interactive mode selected\n"); + sctx->interact = true; + break; + } + } + + if (ret != -1) { + BAD_POPT_PARAMS(pc, poptStrerror(ret), ret, fini); + } + + DEBUG_CLI_INIT(pc_debug); + + CHECK_ROOT(ret, argv[0]); + + /* check username provided */ + if (pc_name == NULL) { + BAD_POPT_PARAMS(pc, _("Username must be specified\n"), ret, fini); + } + + /* check domain is provided */ + if (pc_domain == NULL) { + BAD_POPT_PARAMS(pc, _("Domain must be specified.\n"), ret, fini); + } + + sctx->uctx->domain_name = talloc_strdup(sctx->uctx, pc_domain); + if (sctx->uctx->domain_name == NULL) { + ret = ENOMEM; + goto fini; + } + + sctx->uctx->name = sss_create_internal_fqname(sctx->uctx, + pc_name, pc_domain); + if (sctx->uctx->name == NULL) { + ret = ENOMEM; + goto fini; + } + + poptFreeContext(pc); + + ret = EOK; + + /* copy all information provided from popt */ + sctx->uctx->uid = pc_uid; + sctx->uctx->gid = pc_gid; + if (pc_gecos != NULL) { + sctx->uctx->gecos = talloc_strdup(sctx->uctx, pc_gecos); + if (sctx->uctx->gecos == NULL) { + ret = ENOMEM; + goto fini; + } + } + if (pc_home != NULL) { + sctx->uctx->home = talloc_strdup(sctx->uctx, pc_home); + if (sctx->uctx->home == NULL) { + ret = ENOMEM; + goto fini; + } + } + if (pc_shell != NULL) { + sctx->uctx->shell = talloc_strdup(sctx->uctx, pc_shell); + if (sctx->uctx->shell == NULL) { + ret = ENOMEM; + goto fini; + } + } + + /* check if password file provided */ + if (pc_password_file != NULL) { + sctx->password_file = talloc_strdup(sctx, pc_password_file); + if (sctx->password_file == NULL) { + ret = ENOMEM; + goto fini; + } + sctx->password_method = PASS_FILE; + } else { + sctx->password_method = PASS_PROMPT; + } + + *_sctx = talloc_steal(mem_ctx, sctx); + +fini: + talloc_free(tmp_ctx); + return ret; +} + +static int seed_init_db(TALLOC_CTX *mem_ctx, + const char *domain_name, + struct confdb_ctx **_confdb, + struct sss_domain_info **_domain, + struct sysdb_ctx **_sysdb) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *confdb_path = NULL; + struct confdb_ctx *confdb = NULL; + struct sss_domain_info *domain = NULL; + int ret = EOK; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + /* setup confdb */ + confdb_path = talloc_asprintf(tmp_ctx, "%s/%s", DB_PATH, CONFDB_FILE); + if (confdb_path == NULL) { + ret = ENOMEM; + goto done; + } + + ret = confdb_init(tmp_ctx, &confdb, confdb_path); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not initialize connection to the confdb\n"); + ERROR("Could not initialize connection to the confdb\n"); + goto done; + } + + ret = sssd_domain_init(tmp_ctx, confdb, domain_name, DB_PATH, &domain); + if (ret != EOK) { + SYSDB_VERSION_ERROR(ret); + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not initialize connection to domain '%s' in sysdb.%s\n", + domain_name, ret == ENOENT ? " Domain not found." : ""); + ERROR("Could not initialize connection to domain '%1$s' in sysdb.%2$s\n", + domain_name, ret == ENOENT ? " Domain not found." : ""); + + goto done; + } + + *_confdb = talloc_steal(mem_ctx, confdb); + *_domain = domain; + *_sysdb = domain->sysdb; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int seed_domain_user_info(const char *name, + struct sss_domain_info *domain, + bool *is_cached) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct passwd *passwd = NULL; + struct ldb_result *res = NULL; + int ret = EOK; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + errno = 0; + passwd = getpwnam(name); + if (passwd == NULL) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, "getpwnam failed [%d] [%s]\n", + ret, strerror(ret)); + goto done; + } + + /* look for user in cache */ + ret = sysdb_getpwnam(tmp_ctx, domain, name, &res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Couldn't lookup user (%s) in the cache\n", name); + goto done; + } + + if (res->count == 0) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "User (%s) wasn't found in the cache\n", name); + *is_cached = false; + ret = ENOENT; + goto done; + } else if (res->count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Multiple user (%s) entries were found in the cache\n", name); + ret = EINVAL; + goto done; + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, "User found in cache\n"); + *is_cached = true; + + errno = 0; + ret = initgroups(name, passwd->pw_gid); + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, "initgroups failed [%d] [%s]\n", + ret, strerror(ret)); + goto done; + } + } + +done: + if (ret == ENOMEM) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate user information\n"); + } + talloc_zfree(tmp_ctx); + return ret; +} + +static int seed_cache_user(struct seed_ctx *sctx) +{ + bool in_transaction = false; + int ret = EOK; + errno_t sret; + + ret = sysdb_transaction_start(sctx->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb transaction start failure\n"); + goto done; + } + + in_transaction = true; + + if (sctx->user_cached == false) { + ret = sysdb_add_user(sctx->domain, sctx->uctx->name, + sctx->uctx->uid, sctx->uctx->gid, + sctx->uctx->gecos, sctx->uctx->home, + sctx->uctx->shell, NULL, NULL, 0, 0); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add user to the cache. (%d)[%s]\n", + ret, strerror(ret)); + ERROR("Failed to create user cache entry\n"); + goto done; + } + } + + ret = sysdb_cache_password(sctx->domain, sctx->uctx->name, + sctx->uctx->password); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to cache password. (%d)[%s]\n", + ret, strerror(ret)); + ERROR("Failed to cache password\n"); + goto done; + } + + ret = sysdb_transaction_commit(sctx->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb transaction commit failure\n"); + goto done; + } + + in_transaction = false; + +done: + if (in_transaction == true) { + sret = sysdb_transaction_cancel(sctx->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to cancel transaction\n"); + } + } + + return ret; +} + +int main(int argc, const char **argv) +{ + struct seed_ctx *sctx = NULL; + struct user_ctx *input_uctx = NULL; + int ret = EOK; + + /* initialize seed context and parse options */ + ret = seed_init(sctx, argc, argv, &sctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE,"Seed init failed [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + /* set up confdb,sysdb and domain */ + ret = seed_init_db(sctx, sctx->uctx->domain_name, &sctx->confdb, + &sctx->domain, &sctx->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to initialize db and domain\n"); + goto done; + } + + /* get user info from domain */ + ret = seed_domain_user_info(sctx->uctx->name, + sctx->domain, &sctx->user_cached); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed lookup of user [%s] in domain [%s]\n", + sctx->uctx->name, sctx->uctx->domain_name); + } + + /* interactive mode to fill in user information */ + if (sctx->interact == true) { + if (sctx->user_cached == true) { + ERROR("User entry already exists in the cache.\n"); + ret = EEXIST; + goto done; + } else { + ret = seed_interactive_input(sctx, sctx->uctx, &input_uctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to get seed input.\n"); + ret = EINVAL; + goto done; + } + talloc_zfree(sctx->uctx); + sctx->uctx = input_uctx; + } + } + + if (sctx->user_cached == false) { + if (sctx->uctx->uid == 0 || sctx->uctx->gid == 0) { + /* require username, UID, and GID to continue */ + DEBUG(SSSDBG_MINOR_FAILURE, "Not enough information provided\n"); + ERROR("UID and primary GID not provided.\n"); + ret = EINVAL; + goto done; + } + } + + /* password input */ + if (sctx->password_method == PASS_FILE) { + ret = seed_password_input_file(sctx->uctx, sctx->password_file, + &sctx->uctx->password); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Password input failure\n"); + goto done; + } + } else { + ret = seed_password_input_prompt(sctx->uctx, &sctx->uctx->password); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Password input failure\n"); + goto done; + } + } + + /* Add user info and password to sysdb cache */ + ret = seed_cache_user(sctx); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to modify cache.\n"); + goto done; + } else { + if (sctx->user_cached == false) { + PRINT("User cache entry created for %1$s\n", sctx->uctx->name); + } + PRINT("Temporary password added to cache entry for %1$s\n", + sctx->uctx->name); + } + +done: + talloc_zfree(sctx); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Exit error: [%d] [%s]\n", + ret, strerror(ret)); + ret = EXIT_FAILURE; + } else { + ret = EXIT_SUCCESS; + } + exit(ret); +} diff --git a/src/tools/sss_signal.c b/src/tools/sss_signal.c new file mode 100644 index 0000000..c4d07f9 --- /dev/null +++ b/src/tools/sss_signal.c @@ -0,0 +1,38 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2014 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdlib.h> +#include <signal.h> + +#include "config.h" +#include "tools/common/sss_process.h" + +int main(int argc, const char **argv) +{ + int ret; + + ret = sss_signal(SIGUSR2); + if (ret != EOK) { + ERROR("Could not signal SSSD. Is SSSD running?\n"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/src/tools/sssctl/sssctl.c b/src/tools/sssctl/sssctl.c new file mode 100644 index 0000000..85d0d09 --- /dev/null +++ b/src/tools/sssctl/sssctl.c @@ -0,0 +1,354 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdlib.h> +#include <stdio.h> +#include <sys/wait.h> + +#include "util/util.h" +#include "tools/sssctl/sssctl.h" +#include "tools/common/sss_tools.h" +#include "tools/common/sss_process.h" + +static const char * +sssctl_prompt_str(enum sssctl_prompt_result result) +{ + switch (result) { + case SSSCTL_PROMPT_YES: + return _("yes"); + case SSSCTL_PROMPT_NO: + return _("no"); + case SSSCTL_PROMPT_ERROR: + return _("error"); + } + + return _("Invalid result."); +} + +enum sssctl_prompt_result +sssctl_prompt(const char *message, + enum sssctl_prompt_result defval) +{ + char answer[255] = {0}; + int c; + const char *yes = sssctl_prompt_str(SSSCTL_PROMPT_YES); + const char *no = sssctl_prompt_str(SSSCTL_PROMPT_NO); + int attempts = 0; + int ret; + + do { + if (defval != SSSCTL_PROMPT_ERROR) { + printf("%s (%s/%s) [%s] ", message, yes, no, + sssctl_prompt_str(defval)); + + /* Detect empty line. */ + c = getchar(); + if (c == '\n') { + return defval; + } else { + ungetc(c, stdin); + } + } else { + printf("%s (%s/%s)", message, yes, no); + } + + ret = scanf("%254s", answer); + + /* Clear stdin. */ + while ((c = getchar()) != '\n' && c != EOF); + + if (ret != 1) { + ERROR("Unable to read user input\n"); + return SSSCTL_PROMPT_ERROR; + } + + + if (strcasecmp(yes, answer) == 0) { + return SSSCTL_PROMPT_YES; + } + + if (strcasecmp(no, answer) == 0) { + return SSSCTL_PROMPT_NO; + } + + ERROR("Invalid input, please provide either " + "'%s' or '%s'.\n", yes, no); + + attempts++; + } while (attempts < 3); + + return SSSCTL_PROMPT_ERROR; +} + +errno_t sssctl_wrap_command(const char *command, + const char *subcommand, + struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + errno_t ret; + + if (subcommand != NULL) { + cmdline->argc++; + } + + const char **args = talloc_array_size(tool_ctx, + sizeof(char *), + cmdline->argc + 2); + if (!args) { + return ENOMEM; + } + + args[0] = command; + + if (subcommand != NULL) { + args[1] = subcommand; + memcpy(&args[2], cmdline->argv, sizeof(char *) * cmdline->argc); + } else { + memcpy(&args[1], cmdline->argv, sizeof(char *) * cmdline->argc); + } + + args[cmdline->argc + 1] = NULL; + + ret = sssctl_run_command(args); + + talloc_free(args); + + return ret; +} + +errno_t sssctl_run_command(const char *const argv[]) +{ + int ret; + int wstatus; + + DEBUG(SSSDBG_TRACE_FUNC, "Running '%s'\n", argv[0]); + + ret = fork(); + if (ret == -1) { + ERROR("Error while executing external command\n"); + return EFAULT; + } + + if (ret == 0) { + /* cast is safe - see + https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html + "The statement about argv[] and envp[] being constants ... " + */ + execvp(argv[0], discard_const_p(char * const, argv)); + ERROR("Error while executing external command\n"); + _exit(1); + } else { + if (waitpid(ret, &wstatus, 0) == -1) { + ERROR("Error while executing external command '%s'\n", argv[0]); + return EFAULT; + } else if (WEXITSTATUS(wstatus) != 0) { + ERROR("Command '%s' failed with [%d]\n", + argv[0], WEXITSTATUS(wstatus)); + return EIO; + } + } + + return EOK; +} + +static errno_t sssctl_manage_service(enum sssctl_svc_action action) +{ +#ifdef HAVE_SYSTEMD + switch (action) { + case SSSCTL_SVC_START: + return sssctl_systemd_start(); + case SSSCTL_SVC_STOP: + return sssctl_systemd_stop(); + case SSSCTL_SVC_RESTART: + return sssctl_systemd_restart(); + } +#elif defined(HAVE_SERVICE) + switch (action) { + case SSSCTL_SVC_START: + return sssctl_run_command( + (const char *[]){SERVICE_PATH, "sssd", "start", NULL}); + case SSSCTL_SVC_STOP: + return sssctl_run_command( + (const char *[]){SERVICE_PATH, "sssd", "stop", NULL}); + case SSSCTL_SVC_RESTART: + return sssctl_run_command( + (const char *[]){SERVICE_PATH, "sssd", "restart", NULL}); + } +#endif + + return ENOSYS; +} + +bool sssctl_start_sssd(bool force) +{ + enum sssctl_prompt_result prompt; + errno_t ret; + + if (sss_daemon_running()) { + return true; + } + + if (!force) { + prompt = sssctl_prompt(_("SSSD needs to be running. Start SSSD now?"), + SSSCTL_PROMPT_YES); + switch (prompt) { + case SSSCTL_PROMPT_YES: + /* continue */ + break; + case SSSCTL_PROMPT_NO: + case SSSCTL_PROMPT_ERROR: + return false; + } + } + + ret = sssctl_manage_service(SSSCTL_SVC_START); + switch(ret) { + case EOK: + return true; + case ENOSYS: + fprintf(stderr, "Starting SSSD automatically is not supported " + "on this platform, please start the service " + "manually\n"); + return false; + default: + fprintf(stderr, "Unable to start SSSD!\n"); + return false; + } + + return true; +} + +bool sssctl_stop_sssd(bool force) +{ + enum sssctl_prompt_result prompt; + errno_t ret; + + if (!sss_daemon_running()) { + return true; + } + + if (!force) { + prompt = sssctl_prompt(_("SSSD must not be running. Stop SSSD now?"), + SSSCTL_PROMPT_YES); + switch (prompt) { + case SSSCTL_PROMPT_YES: + /* continue */ + break; + case SSSCTL_PROMPT_NO: + case SSSCTL_PROMPT_ERROR: + return false; + } + } + + ret = sssctl_manage_service(SSSCTL_SVC_STOP); + switch(ret) { + case EOK: + return true; + case ENOSYS: + fprintf(stderr, "Stopping SSSD automatically is not supported " + "on this platform, please stop the service " + "manually\n"); + return false; + default: + fprintf(stderr, "Unable to stop SSSD!\n"); + return false; + } + + + return true; +} + +bool sssctl_restart_sssd(bool force) +{ + enum sssctl_prompt_result prompt; + errno_t ret; + + if (!force) { + prompt = sssctl_prompt(_("SSSD needs to be restarted. Restart SSSD now?"), + SSSCTL_PROMPT_YES); + switch (prompt) { + case SSSCTL_PROMPT_YES: + /* continue */ + break; + case SSSCTL_PROMPT_NO: + case SSSCTL_PROMPT_ERROR: + return false; + } + } + + ret = sssctl_manage_service(SSSCTL_SVC_RESTART); + switch(ret) { + case EOK: + return true; + case ENOSYS: + fprintf(stderr, "Restarting SSSD automatically is not supported " + "on this platform, please restart the service " + "manually\n"); + return false; + default: + fprintf(stderr, "Unable to restart SSSD!\n"); + return false; + } + + return true; +} + +int main(int argc, const char **argv) +{ + struct sss_route_cmd commands[] = { + SSS_TOOL_DELIMITER("SSSD Status:"), + SSS_TOOL_COMMAND("domain-list", "List available domains", 0, sssctl_domain_list), + SSS_TOOL_COMMAND("domain-status", "Print information about domain", 0, sssctl_domain_status), + SSS_TOOL_COMMAND_FLAGS("user-checks", "Print information about a user and check authentication", 0, sssctl_user_checks, SSS_TOOL_FLAG_SKIP_CMD_INIT|SSS_TOOL_FLAG_SKIP_ROOT_CHECK), + SSS_TOOL_COMMAND("access-report", "Generate access report for a domain", 0, sssctl_access_report), + SSS_TOOL_DELIMITER("Information about cached content:"), + SSS_TOOL_COMMAND("user-show", "Information about cached user", 0, sssctl_user_show), + SSS_TOOL_COMMAND("group-show", "Information about cached group", 0, sssctl_group_show), + SSS_TOOL_COMMAND("netgroup-show", "Information about cached netgroup", 0, sssctl_netgroup_show), + SSS_TOOL_DELIMITER("Local data tools:"), + SSS_TOOL_COMMAND("client-data-backup", "Backup local data", 0, sssctl_client_data_backup), + SSS_TOOL_COMMAND("client-data-restore", "Restore local data from backup", 0, sssctl_client_data_restore), + SSS_TOOL_COMMAND("cache-remove", "Backup local data and remove cached content", 0, sssctl_cache_remove), + SSS_TOOL_COMMAND("cache-upgrade", "Perform cache upgrade", ERR_SYSDB_VERSION_TOO_OLD, sssctl_cache_upgrade), + SSS_TOOL_COMMAND("cache-expire", "Invalidate cached objects", 0, sssctl_cache_expire), + SSS_TOOL_COMMAND("cache-index", "Manage cache indexes", 0, sssctl_cache_index), + SSS_TOOL_DELIMITER("Log files tools:"), + SSS_TOOL_COMMAND("logs-remove", "Remove existing SSSD log files", 0, sssctl_logs_remove), + SSS_TOOL_COMMAND("logs-fetch", "Archive SSSD log files in tarball", 0, sssctl_logs_fetch), + SSS_TOOL_COMMAND("debug-level", "Change or print information about SSSD debug level", 0, sssctl_debug_level), + SSS_TOOL_COMMAND_FLAGS("analyze", "Analyze logged data", 0, sssctl_analyze, SSS_TOOL_FLAG_SKIP_CMD_INIT|SSS_TOOL_FLAG_SKIP_ROOT_CHECK), +#ifdef HAVE_LIBINI_CONFIG_V1_3 + SSS_TOOL_DELIMITER("Configuration files tools:"), + SSS_TOOL_COMMAND_FLAGS("config-check", "Perform static analysis of SSSD configuration", 0, sssctl_config_check, SSS_TOOL_FLAG_SKIP_CMD_INIT), +#endif + SSS_TOOL_DELIMITER("Certificate related tools:"), + SSS_TOOL_COMMAND_FLAGS("cert-show", "Print information about the certificate", 0, sssctl_cert_show, SSS_TOOL_FLAG_SKIP_CMD_INIT|SSS_TOOL_FLAG_SKIP_ROOT_CHECK), + SSS_TOOL_COMMAND("cert-map", "Show users mapped to the certificate", 0, sssctl_cert_map), + SSS_TOOL_COMMAND_FLAGS("cert-eval-rule", "Check mapping and matching rule with a certificate", 0, sssctl_cert_eval_rule, SSS_TOOL_FLAG_SKIP_CMD_INIT|SSS_TOOL_FLAG_SKIP_ROOT_CHECK), +#ifdef BUILD_PASSKEY + SSS_TOOL_DELIMITER("Passkey related tools:"), + SSS_TOOL_COMMAND_FLAGS("passkey-register", "Perform passkey registration", 0, sssctl_passkey_register, SSS_TOOL_FLAG_SKIP_CMD_INIT|SSS_TOOL_FLAG_SKIP_ROOT_CHECK), +#endif + SSS_TOOL_LAST + }; + + return sss_tool_main(argc, argv, commands, NULL); +} diff --git a/src/tools/sssctl/sssctl.h b/src/tools/sssctl/sssctl.h new file mode 100644 index 0000000..3a53a89 --- /dev/null +++ b/src/tools/sssctl/sssctl.h @@ -0,0 +1,152 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SSSCTL_H_ +#define _SSSCTL_H_ + +#include "tools/common/sss_tools.h" + +#define PRINT_IFP_WARNING(ret) do { \ + if (ret == ERR_SBUS_UNKNOWN_SERVICE || ret == ERR_SBUS_NO_REPLY || ret == ETIMEDOUT) { \ + fprintf(stderr, _("InfoPipe operation failed. Check that SSSD " \ + "is running and the InfoPipe responder is enabled. Make sure " \ + "'ifp' is listed in the 'services' option in sssd.conf.")); \ + } \ +} while (0) + +enum sssctl_prompt_result { + SSSCTL_PROMPT_YES, + SSSCTL_PROMPT_NO, + SSSCTL_PROMPT_ERROR +}; + +enum sssctl_svc_action { + SSSCTL_SVC_START, + SSSCTL_SVC_STOP, + SSSCTL_SVC_RESTART +}; + +enum sssctl_prompt_result +sssctl_prompt(const char *message, + enum sssctl_prompt_result defval); + +errno_t sssctl_wrap_command(const char *command, + const char *subcommand, + struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); +errno_t sssctl_run_command(const char *const argv[]); /* argv[0] - command */ +bool sssctl_start_sssd(bool force); +bool sssctl_stop_sssd(bool force); +bool sssctl_restart_sssd(bool force); + +errno_t sssctl_systemd_start(void); +errno_t sssctl_systemd_stop(void); +errno_t sssctl_systemd_restart(void); + +errno_t sssctl_domain_list(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_domain_status(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_client_data_backup(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_client_data_restore(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_cache_remove(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_cache_upgrade(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_cache_expire(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_cache_index(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_logs_remove(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_logs_fetch(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_debug_level(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_analyze(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_user_show(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_group_show(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_netgroup_show(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_config_check(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_user_checks(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_access_report(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_cert_show(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + +errno_t sssctl_cert_map(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); +#ifdef BUILD_PASSKEY +errno_t sssctl_passkey_register(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); +#endif /* BUILD_PASSKEY */ + +errno_t sssctl_cert_eval_rule(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); +#endif /* _SSSCTL_H_ */ diff --git a/src/tools/sssctl/sssctl_access_report.c b/src/tools/sssctl/sssctl_access_report.c new file mode 100644 index 0000000..5ae873d --- /dev/null +++ b/src/tools/sssctl/sssctl_access_report.c @@ -0,0 +1,422 @@ +/* + Copyright (C) 2017 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "tools/common/sss_tools.h" +#include "tools/sssctl/sssctl.h" +#include "sbus/sbus_opath.h" +#include "responder/ifp/ifp_iface/ifp_iface_sync.h" + +/* + * We're searching the cache directly.. + */ +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ipa/ipa_rules_common.h" + +typedef errno_t (*sssctl_dom_access_reporter_fn)(struct sss_tool_ctx *tool_ctx, + struct sss_domain_info *domain); + +static errno_t get_rdn_value(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + const char *dn_attr, + const char **_rdn_value) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + struct ldb_dn *dn = NULL; + const struct ldb_val *rdn_val; + const char *rdn_str; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + dn = ldb_dn_new(tmp_ctx, sysdb_ctx_get_ldb(dom->sysdb), dn_attr); + if (dn == NULL) { + ret = ENOMEM; + goto done; + } + + rdn_val = ldb_dn_get_rdn_val(dn); + if (rdn_val == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "No RDN value?\n"); + ret = ENOMEM; + goto done; + } + + rdn_str = talloc_strndup(tmp_ctx, + (const char *)rdn_val->data, + rdn_val->length); + if (rdn_str == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + *_rdn_value = talloc_steal(mem_ctx, rdn_str); +done: + talloc_zfree(tmp_ctx); + return ret; +} + +static errno_t is_member_group(struct sss_domain_info *dom, + const char *dn_attr, + const char *group_rdn, + bool *_is_group) +{ + const char *comp_name; + const struct ldb_val *comp_val; + TALLOC_CTX *tmp_ctx; + bool is_group = false; + errno_t ret; + struct ldb_dn *dn = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + dn = ldb_dn_new(tmp_ctx, sysdb_ctx_get_ldb(dom->sysdb), dn_attr); + if (dn == NULL) { + ret = ENOMEM; + goto done; + } + + comp_name = ldb_dn_get_component_name(dn, 1); + comp_val = ldb_dn_get_component_val(dn, 1); + if (strcasecmp("cn", comp_name) == 0 + && strncasecmp(group_rdn, + (const char *) comp_val->data, + comp_val->length) == 0) { + is_group = true; + } + + ret = EOK; +done: + *_is_group = is_group; + talloc_zfree(tmp_ctx); + return ret; +} + +static void print_category(struct sss_domain_info *domain, + struct ldb_message *rule_msg, + const char *category_attr_name, + const char *category_label) +{ + struct ldb_message_element *category_attr; + + category_attr = ldb_msg_find_element(rule_msg, category_attr_name); + if (category_attr == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot find %s\n", category_attr_name); + return; + } + + if (category_attr->num_values > 0) { + PRINT("\t%s: ", category_label); + for (unsigned i = 0; i < category_attr->num_values; i++) { + PRINT("%s%s", + i > 0 ? ", " : "", + (const char *) category_attr->values[i].data); + } + PRINT("\n"); + } +} + +static void print_member_attr(struct sss_domain_info *domain, + struct ldb_message *rule_msg, + const char *member_attr_name, + const char *group_rdn, + const char *object_label, + const char *group_label) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + const char **member_names = NULL; + size_t name_count = 0; + const char **member_group_names = NULL; + size_t group_count = 0; + struct ldb_message_element *member_attr = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return; + } + + member_attr = ldb_msg_find_element(rule_msg, member_attr_name); + if (member_attr == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot find %s\n", member_attr_name); + goto done; + } + + member_names = talloc_zero_array(tmp_ctx, + const char *, + member_attr->num_values + 1); + member_group_names = talloc_zero_array(tmp_ctx, + const char *, + member_attr->num_values + 1); + if (member_names == NULL || member_group_names == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "OOM?\n"); + goto done; + } + + for (size_t i = 0; i < member_attr->num_values; i++) { + bool is_group; + const char *rdn_string; + const char *dn_attr; + + dn_attr = (const char *) member_attr->values[i].data; + + ret = is_member_group(domain, dn_attr, group_rdn, &is_group); + if (ret != EOK) { + continue; + } + + ret = get_rdn_value(tmp_ctx, domain, dn_attr, &rdn_string); + if (ret != EOK) { + continue; + } + + if (is_group == false) { + member_names[name_count] = talloc_steal(member_names, + rdn_string); + if (member_names[name_count] == NULL) { + goto done; + } + name_count++; + } else { + member_group_names[group_count] = talloc_strdup(member_group_names, + rdn_string); + if (member_group_names[group_count] == NULL) { + goto done; + } + group_count++; + } + } + + if (member_names[0] != NULL) { + PRINT("\t%s: ", object_label); + for (int i = 0; member_names[i]; i++) { + PRINT("%s%s", i > 0 ? ", " : "", member_names[i]); + } + PRINT("\n"); + } + + if (member_group_names[0] != NULL) { + PRINT("\t%s: ", group_label); + for (int i = 0; member_group_names[i]; i++) { + PRINT("%s%s", i > 0 ? ", " : "", member_group_names[i]); + } + PRINT("\n"); + } + +done: + talloc_free(tmp_ctx); +} + +static void print_ipa_hbac_rule(struct sss_domain_info *domain, + struct ldb_message *rule_msg) +{ + struct ldb_message_element *el; + + el = ldb_msg_find_element(rule_msg, IPA_CN); + if (el == NULL || el->num_values < 1) { + DEBUG(SSSDBG_MINOR_FAILURE, "A rule with no name\n"); + return; + } + + PRINT("Rule name: %1$s\n", el->values[0].data); + + print_member_attr(domain, + rule_msg, + IPA_MEMBER_USER, + "groups", + _("Member users"), + _("Member groups")); + print_category(domain, + rule_msg, + IPA_USER_CATEGORY, + _("User category")); + + print_member_attr(domain, + rule_msg, + IPA_MEMBER_SERVICE, + "hbacservicegroups", + _("Member services"), + _("Member service groups")); + print_category(domain, + rule_msg, + IPA_SERVICE_CATEGORY, + _("Service category")); + + PRINT("\n"); +} + +static errno_t refresh_hbac_rules(struct sss_tool_ctx *tool_ctx, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + struct sbus_sync_connection *conn; + const char *path; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + path = sbus_opath_compose(tmp_ctx, IFP_PATH_DOMAINS, domain->name); + if (path == NULL) { + PRINT("Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + conn = sbus_sync_connect_system(tmp_ctx, NULL); + if (conn == NULL) { + ERROR("Unable to connect to system bus!\n"); + ret = EIO; + goto done; + } + + ret = sbus_call_ifp_domain_RefreshAccessRules(conn, IFP_BUS, path); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to refresh HBAC rules [%d]: %s\n", + ret, sss_strerror(ret)); + PRINT_IFP_WARNING(ret); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sssctl_ipa_access_report(struct sss_tool_ctx *tool_ctx, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *filter = NULL; + errno_t ret; + const char *attrs[] = { + OBJECTCLASS, + IPA_CN, + IPA_MEMBER_USER, + IPA_USER_CATEGORY, + IPA_MEMBER_SERVICE, + IPA_SERVICE_CATEGORY, + IPA_MEMBER_HOST, + IPA_HOST_CATEGORY, + NULL, + }; + size_t rule_count; + struct ldb_message **msgs = NULL; + + /* Run the pam account phase to make sure the rules are fetched by SSSD */ + ret = refresh_hbac_rules(tool_ctx, domain); + if (ret != EOK) { + ERROR("Unable to refresh HBAC rules, using cached content\n"); + /* Non-fatal */ + } + + tmp_ctx = talloc_new(tool_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + filter = talloc_asprintf(tmp_ctx, "(objectClass=%s)", IPA_HBAC_RULE); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_custom(tmp_ctx, domain, filter, + HBAC_RULES_SUBDIR, attrs, + &rule_count, &msgs); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error looking up HBAC rules\n"); + goto done; + } + + if (ret == ENOENT) { + PRINT("No cached rules. All users will be denied access\n"); + ret = EOK; + goto done; + } + + PRINT("%1$zu rules cached\n\n", rule_count); + + for (size_t i = 0; i < rule_count; i++) { + print_ipa_hbac_rule(domain, msgs[i]); + } + + ret = EOK; +done: + talloc_zfree(tmp_ctx); + return ret; +} + +sssctl_dom_access_reporter_fn get_report_fn(const char *provider) +{ + if (strcmp(provider, "ipa") == 0) { + return sssctl_ipa_access_report; + } + + return NULL; +} + +errno_t sssctl_access_report(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + errno_t ret; + const char *domname = NULL; + sssctl_dom_access_reporter_fn reporter; + struct sss_domain_info *dom; + + ret = sss_tool_popt_ex(cmdline, NULL, SSS_TOOL_OPT_OPTIONAL, + NULL, NULL, "DOMAIN", _("Specify domain name."), + SSS_TOOL_OPT_REQUIRED, &domname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + goto done; + } + + dom = find_domain_by_name(tool_ctx->domains, domname, true); + if (dom == NULL) { + ERROR("Cannot find domain %1$s\n", domname); + ret = ERR_DOMAIN_NOT_FOUND; + goto done; + } + + reporter = get_report_fn(dom->provider); + if (reporter == NULL) { + ERROR("Access report not implemented for domains of type %1$s\n", + dom->provider); + goto done; + } + + ret = reporter(tool_ctx, dom); + +done: + free(discard_const(domname)); + + return ret; +} diff --git a/src/tools/sssctl/sssctl_cache.c b/src/tools/sssctl/sssctl_cache.c new file mode 100644 index 0000000..5a62a26 --- /dev/null +++ b/src/tools/sssctl/sssctl_cache.c @@ -0,0 +1,718 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <popt.h> +#include <stdio.h> +#include <time.h> +#include <errno.h> + +#include "util/util.h" +#include "db/sysdb.h" +#include "tools/common/sss_tools.h" +#include "tools/sssctl/sssctl.h" + +#define NOT_FOUND_MSG(obj) _(obj " %s is not present in cache.\n") + +#define SSSCTL_CACHE_NAME {_("Name"), SYSDB_NAME, get_attr_name} +#define SSSCTL_CACHE_CREATE {_("Cache entry creation date"), SYSDB_CREATE_TIME, get_attr_time} +#define SSSCTL_CACHE_UPDATE {_("Cache entry last update time"), SYSDB_LAST_UPDATE, get_attr_time} +#define SSSCTL_CACHE_EXPIRE {_("Cache entry expiration time"), SYSDB_CACHE_EXPIRE, get_attr_expire} +#define SSSCTL_CACHE_IFP {_("Cached in InfoPipe"), SYSDB_IFP_CACHED, get_attr_yesno} +#define SSSCTL_CACHE_NULL {NULL, NULL, NULL} + +enum cache_object { + CACHED_USER, + CACHED_GROUP, + CACHED_NETGROUP, +}; + +typedef errno_t (*sssctl_attr_fn)(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *entry, + struct sss_domain_info *dom, + const char *attr, + const char **_value); + +typedef struct ldb_dn *(*sssctl_basedn_fn)(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain); + +struct sssctl_object_info { + const char *msg; + const char *attr; + sssctl_attr_fn attr_fn; +}; + +static errno_t time_to_string(TALLOC_CTX *mem_ctx, + time_t timestamp, + const char **_value) +{ + const char *value; + struct tm *tm; + char str[255]; + size_t ret; + + tm = localtime(×tamp); + if (tm == NULL) { + return ENOMEM; + } + + ret = strftime(str, 255, "%x %X", tm); + if (ret == 0) { + return ERANGE; + } + + value = talloc_strdup(mem_ctx, str); + if (value == NULL) { + return ENOMEM; + } + + *_value = value; + + return EOK; +} + +static errno_t get_attr_name(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *entry, + struct sss_domain_info *dom, + const char *attr, + const char **_value) +{ + errno_t ret; + const char *orig_name; + char *tmp_name; + char *outname; + + ret = sysdb_attrs_get_string(entry, attr, &orig_name); + if (ret != EOK) { + return ret; + } + + tmp_name = sss_output_name(mem_ctx, orig_name, dom->case_preserve, 0); + if (tmp_name == NULL) { + return ENOMEM; + } + + if (dom->fqnames) { + outname = sss_tc_fqname(mem_ctx, dom->names, dom, tmp_name); + talloc_free(tmp_name); + if (outname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_tc_fqname() failed\n"); + return ENOMEM; + } + } else { + outname = tmp_name; + } + + *_value = outname; + return EOK; +} + +static errno_t get_attr_time(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *entry, + struct sss_domain_info *dom, + const char *attr, + const char **_value) +{ + uint32_t value; + errno_t ret; + + ret = sysdb_attrs_get_uint32_t(entry, attr, &value); + if (ret != EOK) { + return ret; + } + + return time_to_string(mem_ctx, value, _value); +} + +static errno_t get_attr_expire(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *entry, + struct sss_domain_info *dom, + const char *attr, + const char **_value) +{ + uint32_t value; + errno_t ret; + + ret = sysdb_attrs_get_uint32_t(entry, attr, &value); + if (ret != EOK) { + return ret; + } + + if (is_files_provider(dom)) { + *_value = "Never"; + return EOK; + } + + if (value < time(NULL)) { + *_value = "Expired"; + return EOK; + } + + return time_to_string(mem_ctx, value, _value); +} + +static errno_t attr_initgr(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *entry, + struct sss_domain_info *dom, + const char *attr, + const char **_value) +{ + uint32_t value; + errno_t ret; + + ret = sysdb_attrs_get_uint32_t(entry, attr, &value); + if (ret == ENOENT || (ret == EOK && value == 0)) { + *_value = "Initgroups were not yet performed"; + return EOK; + } else if (ret != EOK) { + return ret; + } + + if (is_files_provider(dom)) { + *_value = "Never"; + return EOK; + } + + if (value < time(NULL)) { + *_value = "Expired"; + return EOK; + } + + return time_to_string(mem_ctx, value, _value); +} + +static errno_t get_attr_yesno(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *entry, + struct sss_domain_info *dom, + const char *attr, + const char **_value) +{ + errno_t ret; + bool val; + + ret = sysdb_attrs_get_bool(entry, attr, &val); + if (ret == ENOENT) { + val = 0; + } else if (ret != EOK) { + return ret; + } + + *_value = val ? "Yes" : "No"; + + return EOK; +} + +static const char **sssctl_build_attrs(TALLOC_CTX *mem_ctx, + struct sssctl_object_info *info) +{ + const char **attrs; + size_t count; + int i; + + for (count = 0; info[count].attr != NULL; count++) { + /* no op */ + } + + attrs = talloc_zero_array(mem_ctx, const char *, count + 1); + if (attrs == NULL) { + return NULL; + } + + for (i = 0; i < count; i++) { + attrs[i] = talloc_strdup(attrs, info[i].attr); + if (attrs[i] == NULL) { + talloc_free(attrs); + return NULL; + } + } + + return attrs; +} + +static errno_t sssctl_query_cache(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct ldb_dn *base_dn, + const char *filter, + const char **attrs, + struct sysdb_attrs **_entry) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs **sysdb_attrs; + struct ldb_message **msgs; + size_t count; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + ret = sysdb_search_entry(tmp_ctx, sysdb, base_dn, LDB_SCOPE_SUBTREE, + filter, attrs, &count, &msgs); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, "No result\n"); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to search sysdb " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + if (count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "Search returned more than one result!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = sysdb_msg2attrs(tmp_ctx, count, msgs, &sysdb_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to convert message to sysdb attrs " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + *_entry = talloc_steal(mem_ctx, sysdb_attrs[0]); + +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char *sssctl_create_filter(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + enum cache_object obj_type, + const char *attr_name, + const char *attr_value) +{ + const char *class; + const char *filter; + char *filter_value; + bool qualify_attr = false; + + if (strcmp(attr_name, SYSDB_NAME) == 0) { + if (obj_type == CACHED_USER || obj_type == CACHED_GROUP) { + qualify_attr = true; + } + } + + switch (obj_type) { + case CACHED_USER: + class = SYSDB_USER_CLASS; + break; + case CACHED_GROUP: + class = SYSDB_GROUP_CLASS; + break; + case CACHED_NETGROUP: + class = SYSDB_NETGROUP_CLASS; + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, + "sssctl doesn't handle this object type (type=%d)\n", obj_type); + return NULL; + } + + if (qualify_attr) { + filter_value = sss_create_internal_fqname(NULL, attr_value, dom->name); + } else { + filter_value = talloc_strdup(NULL, attr_value); + } + if (filter_value == NULL) { + return NULL; + } + + if (dom->case_sensitive == false) { + char *filter_value_old; + + filter_value_old = filter_value; + filter_value = sss_tc_utf8_str_tolower(mem_ctx, filter_value_old); + talloc_free(filter_value_old); + } + + filter = talloc_asprintf(mem_ctx, "(&(%s=%s)(|(%s=%s)(%s=%s)))", + obj_type == CACHED_NETGROUP ? SYSDB_OBJECTCLASS : SYSDB_OBJECTCATEGORY, + class, attr_name, filter_value, + SYSDB_NAME_ALIAS, filter_value); + + talloc_free(filter_value); + + return filter; +} + +static errno_t sssctl_find_object(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domains, + struct sss_domain_info *domain, + sssctl_basedn_fn basedn_fn, + enum cache_object obj_type, + const char *attr_name, + const char *attr_value, + const char **attrs, + struct sysdb_attrs **_entry, + struct sss_domain_info **_dom) +{ + TALLOC_CTX *tmp_ctx; + struct sss_domain_info *dom; + struct sysdb_attrs *entry = NULL; + struct ldb_dn *base_dn; + bool fqn_provided; + const char *filter; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + dom = domain == NULL ? domains : domain; + fqn_provided = domain == NULL ? false : true; + while (dom != NULL) { + if (!fqn_provided && dom->fqnames) { + dom = get_next_domain(dom, 0); + continue; + } + + base_dn = basedn_fn(tmp_ctx, dom); + if (base_dn == NULL) { + ret = ENOMEM; + goto done; + } + + filter = sssctl_create_filter(tmp_ctx, dom, obj_type, + attr_name, attr_value); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create filter\n"); + ret = ENOMEM; + goto done; + } + + ret = sssctl_query_cache(tmp_ctx, dom->sysdb, base_dn, filter, + attrs, &entry); + switch(ret) { + case EOK: + /* Entry was found. */ + *_entry = talloc_steal(mem_ctx, entry); + *_dom = dom; + goto done; + case ENOENT: + if (fqn_provided) { + /* Not found but a domain was provided in input. We're done. */ + goto done; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to query cache [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + dom = get_next_domain(dom, 0); + } + + ret = ENOENT; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t sssctl_fetch_object(TALLOC_CTX *mem_ctx, + struct sssctl_object_info *info, + struct sss_domain_info *domains, + struct sss_domain_info *domain, + sssctl_basedn_fn basedn_fn, + enum cache_object obj_type, + const char *attr_name, + const char *attr_value, + struct sysdb_attrs **_entry, + struct sss_domain_info **_dom) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs *entry = NULL; + struct sss_domain_info *dom = NULL; + const char **attrs; + char *sanitized; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = sss_filter_sanitize(tmp_ctx, attr_value, &sanitized); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to sanitize input [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + attrs = sssctl_build_attrs(tmp_ctx, info); + if (attrs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get attribute list!\n"); + ret = ENOMEM; + goto done; + } + + ret = sssctl_find_object(tmp_ctx, domains, domain, basedn_fn, + obj_type, attr_name, sanitized, attrs, + &entry, &dom); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to query cache [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + *_entry = talloc_steal(mem_ctx, entry); + *_dom = dom; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t sssctl_print_object(struct sssctl_object_info *info, + struct sss_domain_info *domains, + struct sss_domain_info *domain, + sssctl_basedn_fn basedn_fn, + const char *noent_fmt, + enum cache_object obj_type, + const char *attr_name, + const char *attr_value) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs *entry = NULL; + const char *value; + errno_t ret; + int i; + struct sss_domain_info *dom = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = sssctl_fetch_object(tmp_ctx, info, domains, domain, basedn_fn, + obj_type, attr_name, attr_value, + &entry, &dom); + if (ret == ENOENT) { + printf(noent_fmt, attr_value); + ret = EOK; + goto done; + } else if (ret != EOK) { + ERROR("Error: Unable to get object [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (dom == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not determine object domain\n"); + ret = ERR_DOMAIN_NOT_FOUND; + goto done; + } + + for (i = 0; info[i].attr != NULL; i++) { + ret = info[i].attr_fn(tmp_ctx, entry, dom, info[i].attr, &value); + if (ret == ENOENT) { + continue; + } else if (ret != EOK) { + ERROR("%s: Unable to read value [%d]: %s\n", + info[i].msg, ret, sss_strerror(ret)); + continue; + } + + printf("%s: %s\n", info[i].msg, value); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t parse_cmdline(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + struct poptOption *options, + const char **_orig_name, + struct sss_domain_info **_domain) +{ + const char *input_name = NULL; + const char *orig_name; + struct sss_domain_info *domain; + int ret; + + ret = sss_tool_popt_ex(cmdline, options, SSS_TOOL_OPT_OPTIONAL, + NULL, NULL, "NAME", _("Specify name."), + SSS_TOOL_OPT_REQUIRED, &input_name, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + goto done; + } + + ret = sss_tool_parse_name(tool_ctx, tool_ctx, input_name, + &orig_name, &domain); + if (ret != EOK) { + ERROR("Unable to parse name %s.\n", input_name); + goto done; + } + + *_orig_name = orig_name; + *_domain = domain; + +done: + free(discard_const(input_name)); + + return ret; +} + +struct sssctl_cache_opts { + struct sss_domain_info *domain; + const char *value; + int sid; + int id; +}; + +errno_t sssctl_user_show(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + struct sssctl_cache_opts opts = {0}; + const char *attr; + errno_t ret; + + struct poptOption options[] = { + {"sid", 's', POPT_ARG_NONE , &opts.sid, 0, _("Search by SID"), NULL }, + {"uid", 'u', POPT_ARG_NONE, &opts.id, 0, _("Search by user ID"), NULL }, + POPT_TABLEEND + }; + + struct sssctl_object_info info[] = { + SSSCTL_CACHE_NAME, + SSSCTL_CACHE_CREATE, + SSSCTL_CACHE_UPDATE, + SSSCTL_CACHE_EXPIRE, + {_("Initgroups expiration time"), SYSDB_INITGR_EXPIRE, attr_initgr}, + SSSCTL_CACHE_IFP, + SSSCTL_CACHE_NULL + }; + + ret = parse_cmdline(cmdline, tool_ctx, options, &opts.value, &opts.domain); + if (ret != EOK) { + return ret; + } + + attr = SYSDB_NAME; + if (opts.sid) { + attr = SYSDB_SID; + } else if (opts.id) { + attr = SYSDB_UIDNUM; + } + + ret = sssctl_print_object(info, tool_ctx->domains, opts.domain, + sysdb_user_base_dn, NOT_FOUND_MSG("User"), + CACHED_USER, attr, opts.value); + if (ret != EOK) { + return ret; + } + + + return EOK; +} + +errno_t sssctl_group_show(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + struct sssctl_cache_opts opts = {0}; + const char *attr; + errno_t ret; + + struct poptOption options[] = { + {"sid", 's', POPT_ARG_NONE , &opts.sid, 0, _("Search by SID"), NULL }, + {"gid", 'g', POPT_ARG_NONE, &opts.id, 0, _("Search by group ID"), NULL }, + POPT_TABLEEND + }; + + struct sssctl_object_info info[] = { + SSSCTL_CACHE_NAME, + SSSCTL_CACHE_CREATE, + SSSCTL_CACHE_UPDATE, + SSSCTL_CACHE_EXPIRE, + SSSCTL_CACHE_IFP, + SSSCTL_CACHE_NULL + }; + + ret = parse_cmdline(cmdline, tool_ctx, options, &opts.value, &opts.domain); + if (ret != EOK) { + return ret; + } + + attr = SYSDB_NAME; + if (opts.sid) { + attr = SYSDB_SID; + } else if (opts.id) { + attr = SYSDB_GIDNUM; + } + + ret = sssctl_print_object(info, tool_ctx->domains, opts.domain, + sysdb_group_base_dn, NOT_FOUND_MSG("Group"), + CACHED_GROUP, attr, opts.value); + if (ret != EOK) { + return ret; + } + + + return EOK; +} + +errno_t sssctl_netgroup_show(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + struct sssctl_cache_opts opts = {0}; + errno_t ret; + + struct sssctl_object_info info[] = { + SSSCTL_CACHE_NAME, + SSSCTL_CACHE_CREATE, + SSSCTL_CACHE_UPDATE, + SSSCTL_CACHE_EXPIRE, + SSSCTL_CACHE_NULL + }; + + ret = parse_cmdline(cmdline, tool_ctx, NULL, &opts.value, &opts.domain); + if (ret != EOK) { + return ret; + } + + ret = sssctl_print_object(info, tool_ctx->domains, opts.domain, + sysdb_netgroup_base_dn, NOT_FOUND_MSG("Netgroup"), + CACHED_NETGROUP, SYSDB_NAME, opts.value); + if (ret != EOK) { + return ret; + } + + + return EOK; +} diff --git a/src/tools/sssctl/sssctl_cert.c b/src/tools/sssctl/sssctl_cert.c new file mode 100644 index 0000000..d2cdc2d --- /dev/null +++ b/src/tools/sssctl/sssctl_cert.c @@ -0,0 +1,289 @@ +/* + Authors: + Sumit Bose <sbose@redhat.com> + + Certificate related utilities + + Copyright (C) 2018 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <popt.h> +#include <stdio.h> +#include <talloc.h> + +#include "util/util.h" +#include "tools/common/sss_tools.h" +#include "tools/sssctl/sssctl.h" +#include "lib/certmap/sss_certmap.h" +#include "util/crypto/sss_crypto.h" +#include "responder/ifp/ifp_iface/ifp_iface_sync.h" + +#define PEM_HEAD "-----BEGIN CERTIFICATE-----\n" +#define PEM_FOOT "-----END CERTIFICATE-----" + +errno_t sssctl_cert_show(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + int verbose = 0; + const char *cert_b64 = NULL; + char *desc; + uint8_t *der_cert = NULL; + size_t der_size; + + /* Parse command line. */ + struct poptOption options[] = { + {"verbose", 'v', POPT_ARG_NONE, &verbose, 0, _("Show debug information"), NULL }, + POPT_TABLEEND + }; + + ret = sss_tool_popt_ex(cmdline, options, SSS_TOOL_OPT_OPTIONAL, + NULL, NULL, "CERTIFICATE-BASE64-ENCODED", + _("Specify base64 encoded certificate."), + SSS_TOOL_OPT_REQUIRED, &cert_b64, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + der_cert = sss_base64_decode(tmp_ctx, cert_b64, &der_size); + if (der_cert == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to decode base64 string.\n"); + ret = EINVAL; + goto done; + } + + ret = sss_certmap_display_cert_content(tmp_ctx, der_cert, der_size, &desc); + if (ret != 0) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to parsed certificate.\n"); + goto done; + } + + printf("%s\n", desc); + ret = EOK; + +done: + talloc_free(tmp_ctx); + free(discard_const(cert_b64)); + + return ret; +} + +errno_t sssctl_cert_map(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + int verbose = 0; + const char *cert_b64 = NULL; + char *cert_pem = NULL; + struct sbus_sync_connection *conn; + const char **paths; + size_t c; + const char *name; + + /* Parse command line. */ + struct poptOption options[] = { + {"verbose", 'v', POPT_ARG_NONE, &verbose, 0, _("Show debug information"), NULL }, + POPT_TABLEEND + }; + + ret = sss_tool_popt_ex(cmdline, options, SSS_TOOL_OPT_OPTIONAL, + NULL, NULL, "CERTIFICATE-BASE64-ENCODED", + _("Specify base64 encoded certificate."), + SSS_TOOL_OPT_REQUIRED, &cert_b64, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + cert_pem = talloc_asprintf(tmp_ctx, "%s%s\n%s", + PEM_HEAD, cert_b64, PEM_FOOT); + if (cert_pem == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + conn = sbus_sync_connect_system(tmp_ctx, NULL); + if (conn == NULL) { + ERROR("Unable to connect to system bus!\n"); + ret = EIO; + goto done; + } + + ret = sbus_call_ifp_users_ListByCertificate(tmp_ctx, conn, IFP_BUS, + IFP_PATH_USERS, cert_pem, -1, + &paths); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to map certificate [%d]: %s\n", + ret, sss_strerror(ret)); + PRINT_IFP_WARNING(ret); + goto done; + } + + if (paths != NULL) { + for (c = 0; paths[c] != NULL; c++) { + ret = sbus_get_ifp_user_name(tmp_ctx, conn, IFP_BUS, paths[c], + &name); + if (ret != EOK) { + goto done; + } + + puts(name); + } + } else { + PRINT(" - no mapped users found -"); + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + free(discard_const(cert_b64)); + + return ret; +} + +struct priv_sss_debug { + bool verbose; +}; + +void certmap_ext_debug(void *private, const char *file, long line, + const char *function, const char *format, ...) +{ + va_list ap; + struct priv_sss_debug *data = private; + + if (data != NULL && data->verbose) { + va_start(ap, format); + fprintf(stdout, "%s:%ld [%s]: ", file, line, function); + vfprintf(stdout, format, ap); + fprintf(stdout, "\n"); + va_end(ap); + } +} + +errno_t sssctl_cert_eval_rule(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + int verbose = 0; + const char *cert_b64 = NULL; + const char *map = NULL; + const char *match = NULL; + struct sss_certmap_ctx *sss_certmap_ctx = NULL; + struct priv_sss_debug priv_sss_debug; + uint8_t *der_cert = NULL; + size_t der_size; + char *filter = NULL; + char **domains = NULL; + + /* Parse command line. */ + struct poptOption options[] = { + {"map", 'p', POPT_ARG_STRING, &map, 0, _("Mapping rule"), NULL }, + {"match", 't', POPT_ARG_STRING, &match, 0, _("Matching rule"), NULL }, + {"verbose", 'v', POPT_ARG_NONE, &verbose, 0, _("Show debug information"), NULL }, + POPT_TABLEEND + }; + + ret = sss_tool_popt_ex(cmdline, options, SSS_TOOL_OPT_OPTIONAL, + NULL, NULL, "CERTIFICATE-BASE64-ENCODED", + _("Specify base64 encoded certificate."), + SSS_TOOL_OPT_REQUIRED, &cert_b64, NULL); + if (ret != EOK) { + ERROR("Unable to parse command arguments\n"); + return ret; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ERROR("Out of memory!\n"); + return ENOMEM; + } + + priv_sss_debug.verbose = (verbose != 0); + + ret = sss_certmap_init(tmp_ctx, certmap_ext_debug, &priv_sss_debug, + &sss_certmap_ctx); + if (ret != EOK) { + ERROR("Failed to setup certmap context.\n"); + goto done; + } + + ret = sss_certmap_add_rule(sss_certmap_ctx, 1, match, map, NULL); + if (ret != EOK) { + ERROR("Failed to add mapping and matching rules with error [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + der_cert = sss_base64_decode(tmp_ctx, cert_b64, &der_size); + if (der_cert == NULL) { + ERROR("Failed to decode base64 string.\n"); + ret = EINVAL; + goto done; + } + + ret = sss_certmap_match_cert(sss_certmap_ctx, der_cert, der_size); + switch (ret) { + case 0: + PRINT("Certificate matches rule.\n"); + break; + case ENOENT: + PRINT("Certificate does not match rule.\n"); + break; + default: + ERROR("Error during certificate matching [%d][%s].\n", + ret, sss_strerror(ret)); + } + + ret = sss_certmap_get_search_filter(sss_certmap_ctx, der_cert, der_size, + &filter, &domains); + if (ret != 0) { + ERROR("Failed to generate mapping filter [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + PRINT("Mapping filter:\n\n %s\n\n", filter); + sss_certmap_free_filter_and_domains(filter, domains); + + ret = EOK; + +done: + + talloc_free(tmp_ctx); + + return ret; +} diff --git a/src/tools/sssctl/sssctl_config.c b/src/tools/sssctl/sssctl_config.c new file mode 100644 index 0000000..f009151 --- /dev/null +++ b/src/tools/sssctl/sssctl_config.c @@ -0,0 +1,201 @@ +/* + Authors: + Michal Židek <mzidek@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <popt.h> +#include <stdio.h> +#include <ini_configobj.h> + +#include "util/util.h" +#include "util/sss_ini.h" +#include "tools/common/sss_tools.h" +#include "tools/common/sss_process.h" +#include "tools/sssctl/sssctl.h" +#include "confdb/confdb.h" + + + +#ifdef HAVE_LIBINI_CONFIG_V1_3 + +static char *sssctl_config_snippet_path(TALLOC_CTX *ctx, const char *path) +{ + char *tmp = NULL; + const char delimiter = '/'; + char *dpos = NULL; + + tmp = talloc_strdup(ctx, path); + if (!tmp) { + return NULL; + } + + dpos = strrchr(tmp, delimiter); + if (dpos != NULL) { + ++dpos; + *dpos = '\0'; + } else { + *tmp = '\0'; + } + + return talloc_strdup_append(tmp, CONFDB_DEFAULT_CONFIG_DIR_NAME); +} + +errno_t sssctl_config_check(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + errno_t ret; + struct sss_ini *init_data; + struct ref_array *ra_error, *ra_success; + char *msg; + uint32_t i = 0; + size_t num_errors; + size_t num_ra_error, num_ra_success; + char **strs = NULL; + TALLOC_CTX *tmp_ctx = NULL; + const char *config_path = NULL; + const char *config_snippet_path = NULL; + struct poptOption long_options[] = { + {"config", 'c', POPT_ARG_STRING, &config_path, + 0, _("Specify a non-default config file"), NULL}, + {"snippet", 's', POPT_ARG_STRING, &config_snippet_path, + 0, _("Specify a non-default snippet dir (The default is to look in " + "the same place where the main config file is located. For " + "example if the config is set to \"/my/path/sssd.conf\", " + "the snippet dir \"/my/path/conf.d\" is used)"), NULL}, + POPT_TABLEEND + }; + + ret = sss_tool_popt(cmdline, long_options, SSS_TOOL_OPT_OPTIONAL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + return ret; + } + + tmp_ctx = talloc_new(NULL); + init_data = sss_ini_new(tmp_ctx); + if (!init_data) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory.\n"); + ret = ENOMEM; + goto done; + } + + if (config_path == NULL) { + config_path = SSSD_CONFIG_FILE; + } + + if (config_snippet_path == NULL) { + config_snippet_path = sssctl_config_snippet_path(tmp_ctx, config_path); + if (config_snippet_path == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create snippet path\n"); + ret = ENOMEM; + goto done; + } + } + + ret = sss_ini_read_sssd_conf(init_data, + config_path, + config_snippet_path); + + if (ret == ERR_INI_OPEN_FAILED) { + PRINT("Failed to open %s\n", config_path); + goto done; + } + + if (!sss_ini_exists(init_data)) { + PRINT("File %1$s does not exist.\n", config_path); + } + + if (ret == ERR_INI_INVALID_PERMISSION) { + PRINT("File ownership and permissions check failed. " + "Expected root:root and 0600.\n"); + goto done; + } + + if (ret == ERR_INI_PARSE_FAILED) { + PRINT("Failed to load configuration from %s.\n", + config_path); + goto done; + } + + if (ret == ERR_INI_ADD_SNIPPETS_FAILED) { + PRINT("Error while reading configuration directory.\n"); + goto done; + } + + /* Used snippet files */ + ra_success = sss_ini_get_ra_success_list(init_data); + num_ra_success = ref_array_len(ra_success); + if ((sss_ini_exists(init_data) == false) && (num_ra_success == 0)) { + PRINT("There is no configuration.\n"); + ret = ERR_INI_OPEN_FAILED; + goto done; + } + + /* Run validators */ + ret = sss_ini_call_validators_strs(tmp_ctx, init_data, + SSSDDATADIR"/cfg_rules.ini", + &strs, &num_errors); + if (ret) { + PRINT("Failed to run validators"); + goto done; + } + + PRINT("Issues identified by validators: %zu\n", num_errors); + for (i = 0; i < num_errors; i++) { + printf("%s\n", strs[i]); + } + + printf("\n"); + + /* Merging issues */ + ra_error = sss_ini_get_ra_error_list(init_data); + num_ra_error = ref_array_len(ra_error); + + PRINT("Messages generated during configuration merging: %zu\n", num_ra_error); + + i = 0; + while (ref_array_get(ra_error, i, &msg) != NULL) { + printf("%s\n", msg); + i++; + } + + printf("\n"); + + /* Used snippets */ + PRINT("Used configuration snippet files: %zu\n", num_ra_success); + + i = 0; + while (ref_array_get(ra_success, i, &msg) != NULL) { + printf("%s\n", msg); + i++; + } + + if (num_errors != 0 || num_ra_error != 0) { + ret = EINVAL; + } else { + ret = EOK; + } + +done: + talloc_free(tmp_ctx); + return ret; +} +#endif /* HAVE_LIBINI_CONFIG_V1_3 */ diff --git a/src/tools/sssctl/sssctl_data.c b/src/tools/sssctl/sssctl_data.c new file mode 100644 index 0000000..82f80c6 --- /dev/null +++ b/src/tools/sssctl/sssctl_data.c @@ -0,0 +1,538 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <popt.h> +#include <stdio.h> + +#include "util/util.h" +#include "db/sysdb.h" +#include "db/sysdb_private.h" +#include "confdb/confdb.h" +#include "confdb/confdb_private.h" +#include "tools/common/sss_process.h" +#include "tools/sssctl/sssctl.h" + +#define SSS_BACKUP_DIR SSS_STATEDIR "/backup" +#define SSS_BACKUP_USER_OVERRIDES SSS_BACKUP_DIR "/sssd_user_overrides.bak" +#define SSS_BACKUP_GROUP_OVERRIDES SSS_BACKUP_DIR "/sssd_group_overrides.bak" +#define SSS_CACHE "sss_cache" + +struct sssctl_data_opts { + int override; + int restore; + int start; + int stop; + int restart; +}; + +static errno_t sssctl_create_backup_dir(const char *path) +{ + mode_t old_umask; + errno_t ret; + + old_umask = umask(SSS_DFL_X_UMASK); + ret = mkdir(path, 0700); + umask(old_umask); + if (ret != EOK && errno != EEXIST) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to create backup directory " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} + +static bool sssctl_backup_file_exists(const char *file) +{ + return access(file, F_OK) == 0; +} + +static bool sssctl_backup_exist(const char **files) +{ + int i; + + for (i = 0; files[i] != NULL; i++) { + if (sssctl_backup_file_exists(files[i])) { + return true; + } + } + + return false; +} + +static errno_t sssctl_backup(bool force) +{ + const char *files[] = {SSS_BACKUP_USER_OVERRIDES, + SSS_BACKUP_GROUP_OVERRIDES, + NULL}; + enum sssctl_prompt_result prompt; + errno_t ret; + + ret = sssctl_create_backup_dir(SSS_BACKUP_DIR); + if (ret != EOK) { + ERROR("Unable to create backup directory [%d]: %s", + ret, sss_strerror(ret)); + return ret; + } + + if (sssctl_backup_exist(files) && !force) { + prompt = sssctl_prompt(_("SSSD backup of local data already exists, " + "override?"), SSSCTL_PROMPT_NO); + switch (prompt) { + case SSSCTL_PROMPT_YES: + /* continue */ + break; + case SSSCTL_PROMPT_NO: + return EEXIST; + case SSSCTL_PROMPT_ERROR: + return EIO; + } + } + + ret = sssctl_run_command((const char *[]){"sss_override", "user-export", + SSS_BACKUP_USER_OVERRIDES, NULL}); + if (ret != EOK) { + ERROR("Unable to export user overrides\n"); + return ret; + } + + ret = sssctl_run_command((const char *[]){"sss_override", "group-export", + SSS_BACKUP_GROUP_OVERRIDES, NULL}); + if (ret != EOK) { + ERROR("Unable to export group overrides\n"); + return ret; + } + + return ret; +} + +errno_t sssctl_client_data_backup(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + struct sssctl_data_opts opts = {0}; + errno_t ret; + + /* Parse command line. */ + struct poptOption options[] = { + {"override", 'o', POPT_ARG_NONE, &opts.override, 0, _("Override existing backup"), NULL }, + POPT_TABLEEND + }; + + ret = sss_tool_popt(cmdline, options, SSS_TOOL_OPT_OPTIONAL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + return ret; + } + + ret = sssctl_backup(opts.override); + if (ret == EEXIST) { + return EOK; + } + + return ret; +} + +static errno_t sssctl_restore(bool force_start, bool force_restart) +{ + errno_t ret; + + if (!sssctl_start_sssd(force_start)) { + return ERR_SSSD_NOT_RUNNING; + } + + if (sssctl_backup_file_exists(SSS_BACKUP_USER_OVERRIDES)) { + ret = sssctl_run_command((const char *[]){"sss_override", "user-import", + SSS_BACKUP_USER_OVERRIDES, NULL}); + if (ret != EOK) { + ERROR("Unable to import user overrides\n"); + return ret; + } + } + + if (sssctl_backup_file_exists(SSS_BACKUP_USER_OVERRIDES)) { + ret = sssctl_run_command((const char *[]){"sss_override", "group-import", + SSS_BACKUP_GROUP_OVERRIDES, NULL}); + if (ret != EOK) { + ERROR("Unable to import group overrides\n"); + return ret; + } + } + + sssctl_restart_sssd(force_restart); + + ret = EOK; + + return ret; +} + +errno_t sssctl_client_data_restore(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + struct sssctl_data_opts opts = {0}; + errno_t ret; + + /* Parse command line. */ + struct poptOption options[] = { + {"start", 's', POPT_ARG_NONE, &opts.start, 0, _("Start SSSD if it is not running"), NULL }, + {"restart", 'r', POPT_ARG_NONE, &opts.restart, 0, _("Restart SSSD after data import"), NULL }, + POPT_TABLEEND + }; + + ret = sss_tool_popt(cmdline, options, SSS_TOOL_OPT_OPTIONAL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + return ret; + } + + return sssctl_restore(opts.start, opts.restart); +} + +errno_t sssctl_cache_remove(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + struct sssctl_data_opts opts = {0}; + errno_t ret; + + /* Parse command line. */ + struct poptOption options[] = { + {"override", 'o', POPT_ARG_NONE, &opts.override, 0, _("Override existing backup"), NULL }, + {"restore", 'r', POPT_ARG_NONE, &opts.restore, 0, _("Create clean cache files and import local data"), NULL }, + {"stop", 'p', POPT_ARG_NONE, &opts.stop, 0, _("Stop SSSD before removing the cache"), NULL }, + {"start", 's', POPT_ARG_NONE, &opts.start, 0, _("Start SSSD when the cache is removed"), NULL }, + POPT_TABLEEND + }; + + ret = sss_tool_popt(cmdline, options, SSS_TOOL_OPT_OPTIONAL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + return ret; + } + + if (!sssctl_stop_sssd(opts.stop)) { + fprintf(stderr, "Unable to remove the cache unless SSSD is stopped.\n"); + return ERR_SSSD_RUNNING; + } + + PRINT("Creating backup of local data...\n"); + ret = sssctl_backup(opts.override); + if (ret != EOK) { + ERROR("Unable to create backup of local data," + " can not remove the cache.\n"); + return ret; + } + + PRINT("Removing cache files...\n"); + ret = sss_remove_subtree(DB_PATH); + if (ret != EOK) { + ERROR("Unable to remove cache files\n"); + return ret; + } + + if (opts.restore) { + PRINT("Restoring local data...\n"); + sssctl_restore(opts.start, opts.start); + } else { + sssctl_start_sssd(opts.start); + } + + return EOK; +} + +errno_t sssctl_cache_upgrade(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + struct sysdb_upgrade_ctx db_up_ctx; + errno_t ret; + + ret = sss_tool_popt(cmdline, NULL, SSS_TOOL_OPT_OPTIONAL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + return ret; + } + + if (sss_daemon_running()) { + return ERR_SSSD_RUNNING; + } + + ret = confdb_get_domains(tool_ctx->confdb, &tool_ctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "No domains configured.\n"); + return ret; + } + + db_up_ctx.cdb = tool_ctx->confdb; + ret = sysdb_init_ext(tool_ctx, tool_ctx->domains, &db_up_ctx, + true, 0, 0); + if (ret != EOK) { + SYSDB_VERSION_ERROR_DAEMON(ret); + return ret; + } + + return EOK; +} + +errno_t sssctl_cache_expire(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + errno_t ret; + + const char **args = talloc_array_size(tool_ctx, + sizeof(char *), + cmdline->argc + 2); + if (!args) { + return ENOMEM; + } + memcpy(&args[1], cmdline->argv, sizeof(char *) * cmdline->argc); + args[0] = SSS_CACHE; + args[cmdline->argc + 1] = NULL; + + ret = sssctl_run_command(args); + + talloc_free(args); + return ret; +} + +errno_t get_confdb_domains(TALLOC_CTX *ctx, struct confdb_ctx *confdb, + char ***_domains) +{ + int ret; + int domain_count = 0; + int i; + struct sss_domain_info *domain = NULL; + struct sss_domain_info *domain_list = NULL; + char **domains; + + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + /* get domains */ + ret = confdb_get_domains(confdb, &domain_list); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get domain list\n"); + goto done; + } + + for (domain = domain_list; + domain; + domain = get_next_domain(domain, 0)) { + domain_count++; + } + + /* allocate output space */ + domains = talloc_array(tmp_ctx, char *, domain_count + 1); + if (domains == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not allocate memory for domains\n"); + ret = ENOMEM; + goto done; + } + + for (domain = domain_list, i = 0; + domain != NULL; + domain = get_next_domain(domain, 0), i++) { + domains[i] = talloc_asprintf(domains, "%s", domain->name); + if (domains[i] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + ret = ENOMEM; + goto done; + } + } + + /* add NULL to the end */ + domains[i] = NULL; + + *_domains = talloc_steal(ctx, domains); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sssctl_cache_index_action(enum sysdb_index_actions action, + const char **domains, + const char *attr) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + struct confdb_ctx *confdb = NULL; + char *cache; + const char **domain; + const char **index; + const char **indexes = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate the context\n"); + return ENOMEM; + } + + if (domains == NULL) { + /* If the user selected no domain, act on all of them */ + ret = sss_tool_connect_to_confdb(tmp_ctx, &confdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not connect to configuration database.\n"); + goto done; + } + + ret = get_confdb_domains(tmp_ctx, confdb, discard_const(&domains)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not list all the domains.\n"); + goto done; + } + } + + for (domain = domains; *domain != NULL; domain++) { + if (action == SYSDB_IDX_CREATE) { + PRINT("Creating cache index for domain %1$s\n", *domain); + } else if (action == SYSDB_IDX_DELETE) { + PRINT("Deleting cache index for domain %1$s\n", *domain); + } else if (action == SYSDB_IDX_LIST) { + PRINT("Indexes for domain %1$s:\n", *domain); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid action: %i\n", action); + ret = EINVAL; + goto done; + } + + ret = sysdb_get_db_file(tmp_ctx, NULL, *domain, DB_PATH, &cache, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get the cache db name\n"); + goto done; + } + + ret = sysdb_manage_index(tmp_ctx, action, cache, attr, &indexes); + if (ret != EOK) { + goto done; + } + + if (action == SYSDB_IDX_LIST) { + for (index = indexes; *index != NULL; index++) { + PRINT(" Attribute: %1$s\n", *index); + } + talloc_zfree(indexes); + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t sssctl_cache_index(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + const char *attr = NULL; + const char *action_str = NULL; + const char **domains = NULL; + const char **p; + enum sysdb_index_actions action; + errno_t ret; + + /* Parse command line. */ + struct poptOption options[] = { + { "domain", 'd', POPT_ARG_ARGV, &domains, + 0, _("Target a specific domain"), _("domain") }, + { "attribute", 'a', POPT_ARG_STRING, &attr, + 0, _("Attribute to index"), _("attribute") }, + POPT_TABLEEND + }; + + ret = sss_tool_popt_ex(cmdline, options, SSS_TOOL_OPT_OPTIONAL, NULL, NULL, + "ACTION", "create | delete | list", + SSS_TOOL_OPT_REQUIRED, &action_str, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + goto done; + } + + if (action_str == NULL) { + ERROR("Action not provided\n"); + ret = EINVAL; + goto done; + } + + if (strcmp(action_str, "list") == 0) { + action = SYSDB_IDX_LIST; + } else { + if (strcmp(action_str, "create") == 0) { + action = SYSDB_IDX_CREATE; + } else if (strcmp(action_str, "delete") == 0) { + action = SYSDB_IDX_DELETE; + } else { + ERROR("Unknown action: %1$s\nValid actions are " + "\"%2$s\", \"%3$s and \"%4$s\"\n", + action_str, "create", "delete", "list"); + ret = EINVAL; + goto done; + } + + if (attr == NULL) { + ERROR("Attribute (-a) not provided\n"); + ret = EINVAL; + goto done; + } + } + + ret = sssctl_cache_index_action(action, domains, attr); + if (ret == ENOENT) { + ERROR("Attribute %1$s not indexed.\n", attr); + goto done; + } if (ret == EEXIST) { + ERROR("Attribute %1$s already indexed.\n", attr); + goto done; + } else if (ret != EOK) { + ERROR("Index operation failed: %1$s\n", sss_strerror(ret)); + goto done; + } + + if (action != SYSDB_IDX_LIST) { + PRINT("Don't forget to also update the indexes on the remote providers.\n"); + } + + ret = EOK; + +done: + free(discard_const(action_str)); + free(discard_const(attr)); + if (domains != NULL) { + for (p = domains; *p != NULL; p++) { + free(discard_const(*p)); + } + free(discard_const(domains)); + } + + return ret; +} diff --git a/src/tools/sssctl/sssctl_domains.c b/src/tools/sssctl/sssctl_domains.c new file mode 100644 index 0000000..ee8cb13 --- /dev/null +++ b/src/tools/sssctl/sssctl_domains.c @@ -0,0 +1,408 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <popt.h> +#include <stdio.h> +#include <talloc.h> + +#include "util/util.h" +#include "tools/common/sss_tools.h" +#include "tools/sssctl/sssctl.h" +#include "sbus/sbus_opath.h" +#include "responder/ifp/ifp_iface/ifp_iface_sync.h" + +static errno_t +sssctl_domain_list_get_properties(TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *path, + const char **_name, + bool *_is_subdom) +{ + errno_t ret; + + if (_name != NULL) { + ret = sbus_get_ifp_domains_name(mem_ctx, conn, IFP_BUS, path, _name); + if (ret != EOK) { + goto done; + } + } + + if (_is_subdom != NULL) { + ret = sbus_get_ifp_domains_subdomain(conn, IFP_BUS, path, _is_subdom); + if (ret != EOK) { + goto done; + } + } + + ret = EOK; + +done: + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get domain property [%d]: %s\n", + ret, sss_strerror(ret)); + PRINT_IFP_WARNING(ret); + } + + return ret; +} + +errno_t sssctl_domain_list(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + TALLOC_CTX *tmp_ctx; + struct sbus_sync_connection *conn; + const char **paths; + const char *name; + bool is_subdom; + int start = 0; + int verbose = 0; + errno_t ret; + int i; + + /* Parse command line. */ + struct poptOption options[] = { + {"start", 's', POPT_ARG_NONE, &start, 0, _("Start SSSD if it is not running"), NULL }, + {"verbose", 'v', POPT_ARG_NONE, &verbose, 0, _("Show domain list including primary or trusted domain type"), NULL }, + POPT_TABLEEND + }; + + ret = sss_tool_popt(cmdline, options, SSS_TOOL_OPT_OPTIONAL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + return ret; + } + + if (!sssctl_start_sssd(start)) { + return ERR_SSSD_NOT_RUNNING; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + conn = sbus_sync_connect_system(tmp_ctx, NULL); + if (conn == NULL) { + ERROR("Unable to connect to system bus!\n"); + ret = EIO; + goto done; + } + + ret = sbus_call_ifp_ListDomains(tmp_ctx, conn, IFP_BUS, IFP_PATH, &paths); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to list domains [%d]: %s\n", + ret, sss_strerror(ret)); + PRINT_IFP_WARNING(ret); + goto done; + } + + if (verbose) { + for (i = 0; paths[i] != NULL; i++) { + ret = sssctl_domain_list_get_properties(tmp_ctx, conn, paths[i], + &name, &is_subdom); + if (ret != EOK) { + goto done; + } + + if (is_subdom) { + printf("Trusted domain: %s\n", name); + } else { + printf("Primary domain: %s\n", name); + } + } + + return EOK; + } + + for (i = 0; paths[i] != NULL; i++) { + ret = sssctl_domain_list_get_properties(tmp_ctx, conn, paths[i], + &name, NULL); + if (ret != EOK) { + goto done; + } + + puts(name); + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sssctl_domain_status_online(struct sbus_sync_connection *conn, + const char *domain_path) +{ + bool is_online; + errno_t ret; + + ret = sbus_call_ifp_domain_IsOnline(conn, IFP_BUS, domain_path, &is_online); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get domain status [%d]: %s\n", + ret, sss_strerror(ret)); + PRINT_IFP_WARNING(ret); + return ret; + } + + PRINT("Online status: %s\n", is_online ? _("Online") : _("Offline")); + + return EOK; +} + +static const char *proper_service_name(const char *service) +{ + if (strcasecmp(service, "AD_GC") == 0) { + return "AD Global Catalog"; + } else if (strcasecmp(service, "AD") == 0) { + return "AD Domain Controller"; + } else if (strncasecmp(service, "sd_gc_", strlen("sd_gc_")) == 0) { + return "AD Global Catalog"; + } else if (strncasecmp(service, "sd_", strlen("sd_")) == 0) { + return "AD Domain Controller"; + } + + return service; +} + +static errno_t +sssctl_domain_status_active_server(struct sbus_sync_connection *conn, + const char *domain_path) +{ + TALLOC_CTX *tmp_ctx; + const char *server; + const char **services; + errno_t ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = sbus_call_ifp_domain_ListServices(tmp_ctx, conn, IFP_BUS, + domain_path, &services); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get domain services [%d]: %s\n", + ret, sss_strerror(ret)); + PRINT_IFP_WARNING(ret); + goto done; + } + + if (services == NULL) { + PRINT("This domain has no active servers.\n"); + ret = EOK; + goto done; + } + + PRINT("Active servers:\n"); + for (i = 0; services[i] != NULL; i++) { + ret = sbus_call_ifp_domain_ActiveServer(tmp_ctx, conn, IFP_BUS, + domain_path, services[i], &server); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get active server [%d]: %s\n", + ret, sss_strerror(ret)); + PRINT_IFP_WARNING(ret); + goto done; + } + + /* SBUS_REQ_STRING_DEFAULT handles (server == NULL) case gracefully */ + server = SBUS_REQ_STRING_DEFAULT(server, _("not connected")); + printf("%s: %s\n", proper_service_name(services[i]), server); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +sssctl_domain_status_server_list(struct sbus_sync_connection *conn, + const char *domain_path) +{ + TALLOC_CTX *tmp_ctx; + const char **servers; + const char **services; + errno_t ret; + int i, j; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = sbus_call_ifp_domain_ListServices(tmp_ctx, conn, IFP_BUS, + domain_path, &services); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get domain services [%d]: %s\n", + ret, sss_strerror(ret)); + PRINT_IFP_WARNING(ret); + goto done; + } + + if (services == NULL) { + PRINT("No servers discovered.\n"); + ret = EOK; + goto done; + } + + for (i = 0; services[i] != NULL; i++) { + PRINT("Discovered %s servers:\n", proper_service_name(services[i])); + + ret = sbus_call_ifp_domain_ListServers(tmp_ctx, conn, IFP_BUS, + domain_path, services[i], &servers); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get domain servers [%d]: %s\n", + ret, sss_strerror(ret)); + PRINT_IFP_WARNING(ret); + goto done; + } + + if (servers == NULL || servers[0] == NULL) { + PRINT("None so far.\n"); + continue; + } + + for (j = 0; servers[j] != NULL; j++) { + printf("- %s\n", servers[j]); + } + + printf("\n"); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +struct sssctl_domain_status_opts { + const char *domain; + int online; + int last; + int active; + int servers; + int force_start; +}; + +errno_t sssctl_domain_status(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct sssctl_domain_status_opts opts = {0}; + struct sbus_sync_connection *conn; + const char *path; + bool opt_set; + errno_t ret; + + /* Parse command line. */ + struct poptOption options[] = { + {"online", 'o', POPT_ARG_NONE , &opts.online, 0, _("Show online status"), NULL }, + {"active-server", 'a', POPT_ARG_NONE, &opts.active, 0, _("Show information about active server"), NULL }, + {"servers", 'r', POPT_ARG_NONE, &opts.servers, 0, _("Show list of discovered servers"), NULL }, + {"start", 's', POPT_ARG_NONE, &opts.force_start, 0, _("Start SSSD if it is not running"), NULL }, + POPT_TABLEEND + }; + + ret = sss_tool_popt_ex(cmdline, options, SSS_TOOL_OPT_OPTIONAL, + NULL, NULL, "DOMAIN", _("Specify domain name."), + SSS_TOOL_OPT_REQUIRED, &opts.domain, &opt_set); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + goto done; + } + + if (opt_set == false) { + opts.online = true; + opts.last = true; + opts.active = true; + opts.servers = true; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + path = sbus_opath_compose(tmp_ctx, IFP_PATH_DOMAINS, opts.domain); + if (path == NULL) { + PRINT("Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + if (!sssctl_start_sssd(opts.force_start)) { + ret = ERR_SSSD_NOT_RUNNING; + goto done; + } + + conn = sbus_sync_connect_system(tmp_ctx, NULL); + if (conn == NULL) { + ERROR("Unable to connect to system bus!\n"); + ret = EIO; + goto done; + } + + if (opts.online) { + ret = sssctl_domain_status_online(conn, path); + if (ret != EOK) { + ERROR("Unable to get online status\n"); + goto done; + } + + printf("\n"); + } + + if (opts.active) { + ret = sssctl_domain_status_active_server(conn, path); + if (ret != EOK) { + ERROR("Unable to get online status\n"); + goto done; + } + + printf("\n"); + } + + if (opts.servers) { + ret = sssctl_domain_status_server_list(conn, path); + if (ret != EOK) { + ERROR("Unable to get server list\n"); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + free(discard_const(opts.domain)); + + return ret; +} diff --git a/src/tools/sssctl/sssctl_logs.c b/src/tools/sssctl/sssctl_logs.c new file mode 100644 index 0000000..f8f5a65 --- /dev/null +++ b/src/tools/sssctl/sssctl_logs.c @@ -0,0 +1,608 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <stdlib.h> +#include <limits.h> +#include <talloc.h> +#include <sys/types.h> +#include <dirent.h> +#include <ctype.h> +#include <signal.h> +#include <utime.h> +#include <ldb.h> +#include <popt.h> +#include <stdio.h> +#include <glob.h> + +#include "util/util.h" +#include "tools/common/sss_tools.h" +#include "tools/common/sss_process.h" +#include "tools/sssctl/sssctl.h" +#include "tools/tools_util.h" +#include "confdb/confdb.h" +#include "sss_iface/sss_iface_sync.h" +#include "responder/ifp/ifp_iface/ifp_iface_sync.h" + +#define LOG_FILE(file) " " LOG_PATH "/" file +#define LOG_FILES LOG_FILE("*.log") +#define SSS_ANALYZE SSSD_LIBEXEC_PATH"/sss_analyze" + +#define CHECK(expr, done, msg) do { \ + if (expr) { \ + ERROR(msg "\n"); \ + goto done; \ + } \ +} while(0) + +#define POPT_SERV_OPTION(NAME, VAR, DESC) \ + {services[SERV_ ## NAME].name, '\0', POPT_BIT_SET, &VAR, \ + services[SERV_ ## NAME].mask, DESC, NULL} + +#define STARTS_WITH(s, p) (strncmp((s), (p), strlen(p)) == 0) +#define REMOVE_PREFIX(s, p) (STARTS_WITH(s, p) ? (s) + strlen(p) : (s)) +#define IS_DOMAIN(c) STARTS_WITH((c), "domain/") +#define DOMAIN_NAME(c) REMOVE_PREFIX((c), "domain/") +#define EMPTY_TARGETS(t) ((t)[0] == NULL) + +enum debug_level_action { + ACTION_SET, + ACTION_GET +}; + +struct debuglevel_tool_ctx { + struct confdb_ctx *confdb; + char **sections; +}; + +struct sssctl_logs_opts { + int delete; + int archived; +}; + +struct sssctl_service_desc { + const char *name; + int mask; +}; + +enum serv_idx { + SERV_SSSD, + SERV_NSS, + SERV_PAM, + SERV_SUDO, + SERV_AUTOFS, + SERV_SSH, + SERV_PAC, + SERV_IFP, + SERV_COUNT +}; + +struct sssctl_service_desc services[] = { + { "sssd", 1U << SERV_SSSD }, + { "nss", 1U << SERV_NSS }, + { "pam", 1U << SERV_PAM }, + { "sudo", 1U << SERV_SUDO }, + { "autofs", 1U << SERV_AUTOFS}, + { "ssh", 1U << SERV_SSH }, + { "pac", 1U << SERV_PAC }, + { "ifp", 1U << SERV_IFP } +}; + +static struct sbus_sync_connection *connect_to_sbus(TALLOC_CTX *mem_ctx) +{ + struct sbus_sync_connection *conn; + + conn = sbus_sync_connect_private(mem_ctx, SSS_MONITOR_ADDRESS, NULL); + if (conn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to connect to the sbus monitor\n"); + } + + return conn; +} + +static const char *get_busname(TALLOC_CTX *mem_ctx, struct confdb_ctx *confdb, + const char *component) +{ + errno_t ret; + const char *busname; + struct sss_domain_info *domain; + + if (strcmp(component, "sssd") == 0) { + busname = talloc_strdup(mem_ctx, SSS_BUS_MONITOR); + } else if (IS_DOMAIN(component)) { + ret = confdb_get_domain(confdb, DOMAIN_NAME(component), &domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown domain: %s\n", component); + busname = NULL; + goto done; + } + + busname = sss_iface_domain_bus(mem_ctx, domain); + } else { + busname = talloc_asprintf(mem_ctx, "sssd.%s", component); + } + +done: + return busname; +} + +/* in_out_value is an input argument when action is ACTION_SET; it is an output + * argument when action is ACTION_GET. */ +static errno_t do_debug_level(enum debug_level_action action, + struct sbus_sync_connection *conn, + struct confdb_ctx *confdb, + const char *component, + uint32_t *in_out_value) +{ + errno_t ret; + uint32_t value; + const char *busname; + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + busname = get_busname(tmp_ctx, confdb, component); + if (busname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create the bus name for %s\n", + component); + } + + if (action == ACTION_GET) { + ret = sbus_get_service_debug_level(conn, busname, SSS_BUS_PATH, &value); + if (ret != EOK) { + ret = ENOENT; + goto done; + } + + *in_out_value = value; + } else { + ret = sbus_set_service_debug_level(conn, busname, SSS_BUS_PATH, + *in_out_value); + if (ret != EOK) { + ret = ENOENT; + goto done; + } + } + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sssctl_do_debug_level(enum debug_level_action action, + struct debuglevel_tool_ctx *tool_ctx, + const char **targets, + uint32_t debug_to_set) +{ + bool all_targets = EMPTY_TARGETS(targets); + errno_t ret = EOK; + errno_t final_ret = EOK; + uint32_t current_level = SSSDBG_INVALID; + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + const char *stripped_target; + const char **curr_target; + struct sbus_sync_connection *conn; + + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + conn = connect_to_sbus(tmp_ctx); + if (conn == NULL) { + ERROR("SSSD is not running.\n"); + ret = EIO; + goto fini; + } + + curr_target = (all_targets ? + discard_const_p(const char *, tool_ctx->sections) : targets); + while (*curr_target != NULL) { + stripped_target = REMOVE_PREFIX(*curr_target, "config/"); + + if (action == ACTION_GET) { + ret = do_debug_level(ACTION_GET, conn, tool_ctx->confdb, + stripped_target, ¤t_level); + CHECK(ret != EOK && ret != ENOENT, fini, + "Could not read the debug level."); + + if (ret == EOK) { + PRINT(_("%1$-25s %2$#.4x\n"), stripped_target, current_level); + } else { + if (!all_targets) { + if (IS_DOMAIN(stripped_target)) { + PRINT(_("%1$-25s Unknown domain\n"), stripped_target); + } else { + PRINT(_("%1$-25s Unreachable service\n"), stripped_target); + } + final_ret = ENOENT; + } + } + } else { + ret = do_debug_level(ACTION_SET, conn, tool_ctx->confdb, + stripped_target, &debug_to_set); + CHECK(ret != EOK && ret != ENOENT, fini, + "Could not set the debug level."); + if (ret == ENOENT && !all_targets) { + final_ret = ret; + } + } + curr_target++; + } + + if (ret == EOK) { + ret = final_ret; + } + +fini: + talloc_free(tmp_ctx); + return ret; +} + +errno_t get_confdb_sections(TALLOC_CTX *ctx, struct confdb_ctx *confdb, + char ***output_sections) +{ + int ret; + int domain_count = 0; + int i = 0; + struct sss_domain_info *domain = NULL; + struct sss_domain_info *domain_list = NULL; + char **sections; + const char *known_services[] = { + CONFDB_MONITOR_CONF_ENTRY, + CONFDB_NSS_CONF_ENTRY, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAC_CONF_ENTRY, + CONFDB_SSH_CONF_ENTRY, + CONFDB_SUDO_CONF_ENTRY, + CONFDB_AUTOFS_CONF_ENTRY, + CONFDB_IFP_CONF_ENTRY, + }; + static const int known_services_count = sizeof(known_services) + / sizeof(*known_services); + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + /* get domains */ + ret = confdb_get_domains(confdb, &domain_list); + if (ret != EOK) + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get domain list\n"); + + for (domain = domain_list; + domain; + domain = get_next_domain(domain, 0)) { + domain_count++; + } + + /* allocate output space */ + sections = talloc_array(ctx, char *, + domain_count + known_services_count + 1); + if (sections == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not allocate memory for sections\n"); + ret = ENOMEM; + goto fail; + } + + for (i = 0; i < known_services_count; i++) { + sections[i] = talloc_strdup(tmp_ctx, known_services[i]); + if (sections[i] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + ret = ENOMEM; + goto fail; + } + } + + for (domain = domain_list; + domain; + domain = get_next_domain(domain, 0), i++) { + sections[i] = talloc_asprintf(tmp_ctx, CONFDB_DOMAIN_PATH_TMPL, + domain->name); + if (sections[i] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + ret = ENOMEM; + goto fail; + } + } + + /* add NULL to the end */ + sections[i] = NULL; + + *output_sections = talloc_steal(ctx, sections); + + return EOK; +fail: + talloc_free(tmp_ctx); + return ret; +} + +static const char **get_targets(TALLOC_CTX *mem_ctx, int services_mask, + const char **domainv) +{ + int i; + int count = 1; + const char **targets = NULL; + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return NULL; + } + + targets = talloc_zero_array(tmp_ctx, const char *, count); + if (targets == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not allocate memory for the list of targets\n"); + goto done; + } + + if (services_mask != 0) { + for (i = 0; i < SERV_COUNT; i++) { + if (services_mask == 0 || (services_mask & services[i].mask) != 0) { + targets = talloc_realloc(tmp_ctx, targets, const char *, count + 1); + if (targets == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not allocate memory for the list of targets\n"); + goto done; + } + targets[count - 1] = talloc_strdup(tmp_ctx, services[i].name); + targets[count++] = NULL; + } + } + } + + if (domainv != NULL) { + for (; *domainv != NULL; domainv++) { + targets = talloc_realloc(tmp_ctx, targets, const char *, count + 1); + if (targets == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not allocate memory for the list of targets\n"); + goto done; + } + if (IS_DOMAIN(*domainv)) { + targets[count - 1] = talloc_strdup(tmp_ctx, *domainv); + } else { + targets[count - 1] = talloc_asprintf(tmp_ctx, "domain/%s", *domainv); + } + targets[count++] = NULL; + } + } + + targets = talloc_steal(mem_ctx, targets); + for (i = 0; i < count; i++) { + targets[i] = talloc_steal(mem_ctx, targets[i]); + } + +done: + talloc_free(tmp_ctx); + return targets; +} + +int parse_debug_level(const char *strlevel) +{ + long value; + char *endptr; + + errno = 0; + value = strtol(strlevel, &endptr, 0); + if ((errno != 0) || (endptr == strlevel) || (*endptr != '\0')) { + return SSSDBG_INVALID; + } + + return debug_convert_old_level(value); +} + +errno_t sssctl_logs_remove(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + struct sssctl_logs_opts opts = {0}; + errno_t ret; + glob_t globbuf; + + /* Parse command line. */ + struct poptOption options[] = { + {"delete", 'd', POPT_ARG_NONE, &opts.delete, 0, _("Delete log files instead of truncating"), NULL }, + POPT_TABLEEND + }; + + ret = sss_tool_popt(cmdline, options, SSS_TOOL_OPT_OPTIONAL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + return ret; + } + + if (opts.delete) { + PRINT("Deleting log files...\n"); + ret = sss_remove_subtree(LOG_PATH); + if (ret != EOK) { + ERROR("Unable to remove log files\n"); + return ret; + } + + sss_signal(SIGHUP); + } else { + globbuf.gl_offs = 4; + ret = glob(LOG_PATH"/*.log", GLOB_ERR|GLOB_DOOFFS, NULL, &globbuf); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to expand log files list\n"); + return ret; + } + globbuf.gl_pathv[0] = discard_const_p(char, "truncate"); + globbuf.gl_pathv[1] = discard_const_p(char, "--no-create"); + globbuf.gl_pathv[2] = discard_const_p(char, "--size"); + globbuf.gl_pathv[3] = discard_const_p(char, "0"); + + PRINT("Truncating log files...\n"); + ret = sssctl_run_command((const char * const*)globbuf.gl_pathv); + globfree(&globbuf); + if (ret != EOK) { + ERROR("Unable to truncate log files\n"); + return ret; + } + } + + return EOK; +} + +errno_t sssctl_logs_fetch(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + const char *file = NULL; + errno_t ret; + glob_t globbuf; + + /* Parse command line. */ + ret = sss_tool_popt_ex(cmdline, NULL, SSS_TOOL_OPT_OPTIONAL, NULL, NULL, + "FILE", "Output file", SSS_TOOL_OPT_REQUIRED, + &file, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + goto done; + } + + globbuf.gl_offs = 3; + ret = glob(LOG_PATH"/*.log", GLOB_ERR|GLOB_DOOFFS, NULL, &globbuf); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to expand log files list\n"); + goto done; + } + globbuf.gl_pathv[0] = discard_const_p(char, "tar"); + globbuf.gl_pathv[1] = discard_const_p(char, "-czf"); + globbuf.gl_pathv[2] = discard_const_p(char, file); + + PRINT("Archiving log files into %s...\n", file); + ret = sssctl_run_command((const char * const*)globbuf.gl_pathv); + globfree(&globbuf); + if (ret != EOK) { + ERROR("Unable to archive log files\n"); + goto done; + } + +done: + free(discard_const(file)); + + return ret; +} + +errno_t sssctl_debug_level(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + int ret; + int pc_services = 0; + uint32_t debug_to_set = SSSDBG_INVALID; + const char **pc_domains = NULL; + const char **targets = NULL; + const char *debug_as_string = NULL; + + struct debuglevel_tool_ctx *ctx = NULL; + struct poptOption long_options[] = { + {"domain", '\0', POPT_ARG_ARGV, &pc_domains, + 0, _("Target a specific domain"), _("domain")}, + POPT_SERV_OPTION(SSSD, pc_services, _("Target the SSSD service")), + POPT_SERV_OPTION(NSS, pc_services, _("Target the NSS service")), + POPT_SERV_OPTION(PAM, pc_services, _("Target the PAM service")), + POPT_SERV_OPTION(SUDO, pc_services, _("Target the SUDO service")), + POPT_SERV_OPTION(AUTOFS, pc_services, _("Target the AUTOFS service")), + POPT_SERV_OPTION(SSH, pc_services, _("Target the SSH service")), + POPT_SERV_OPTION(PAC, pc_services, _("Target the PAC service")), + POPT_SERV_OPTION(IFP, pc_services, _("Target the IFP service")), + POPT_TABLEEND + }; + + /* allocate context */ + ctx = talloc_zero(NULL, struct debuglevel_tool_ctx); + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not allocate memory for tools context\n"); + ret = ENOMEM; + goto fini; + } + + ret = sss_tool_popt_ex(cmdline, long_options, SSS_TOOL_OPT_OPTIONAL, NULL, + NULL, "DEBUG_LEVEL_TO_SET", + _("Specify debug level you want to set"), + SSS_TOOL_OPT_OPTIONAL, &debug_as_string, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + goto fini; + } + + CHECK_ROOT(ret, debug_prg_name); + + if (debug_as_string != NULL) { + debug_to_set = (uint32_t) parse_debug_level(debug_as_string); + CHECK(debug_to_set == SSSDBG_INVALID, fini, "Invalid debug level."); + } + + /* Create a list with all the target names (services + domains) */ + targets = get_targets(ctx, pc_services, pc_domains); + CHECK(targets == NULL, fini, "Could not allocate memory."); + + ret = sss_tool_connect_to_confdb(ctx, &ctx->confdb); + CHECK(ret != EOK, fini, "Could not connect to configuration database."); + + ret = get_confdb_sections(ctx, ctx->confdb, &ctx->sections); + CHECK(ret != EOK, fini, "Could not get all configuration sections."); + + if (debug_as_string == NULL) { + ret = sssctl_do_debug_level(ACTION_GET, ctx, targets, 0); + } else { + ret = sssctl_do_debug_level(ACTION_SET, ctx, targets, debug_to_set); + } + + /* Only report missing components that the user requested, + except for the monitor (sssd not running) */ + if (ret != ENOENT && ret != EIO && EMPTY_TARGETS(targets)) { + ret = EOK; + } + +fini: + talloc_free(ctx); + free(discard_const(debug_as_string)); + + return ret; +} + +errno_t sssctl_analyze(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ +#ifndef BUILD_CHAIN_ID + PRINT("ERROR: Tevent chain ID support missing, log analyzer is unsupported.\n"); + return EOK; +#endif + errno_t ret; + + ret = sssctl_wrap_command(SSS_ANALYZE, NULL, cmdline, tool_ctx, pvt); + + return ret; +} diff --git a/src/tools/sssctl/sssctl_passkey.c b/src/tools/sssctl/sssctl_passkey.c new file mode 100644 index 0000000..34cab00 --- /dev/null +++ b/src/tools/sssctl/sssctl_passkey.c @@ -0,0 +1,42 @@ +/* + Authors: + Justin Stephenson <jstephen@redhat.com> + + Passkey related utilities + + Copyright (C) 2022 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <popt.h> +#include <stdio.h> +#include <talloc.h> + +#include "util/util.h" +#include "tools/common/sss_tools.h" +#include "tools/sssctl/sssctl.h" + +#define SSS_PASSKEY_CHILD SSSD_LIBEXEC_PATH"/passkey_child" + +errno_t sssctl_passkey_register(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + errno_t ret; + + ret = sssctl_wrap_command(SSS_PASSKEY_CHILD, "--register", cmdline, tool_ctx, pvt); + + return ret; +} diff --git a/src/tools/sssctl/sssctl_systemd.c b/src/tools/sssctl/sssctl_systemd.c new file mode 100644 index 0000000..1d30558 --- /dev/null +++ b/src/tools/sssctl/sssctl_systemd.c @@ -0,0 +1,95 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <talloc.h> +#include <dbus/dbus.h> +#include <errno.h> + +#include "util/util.h" +#include "tools/sssctl/sssctl.h" +#include "sss_iface/sss_iface_sync.h" + +#define SSS_SYSTEMD_BUS "org.freedesktop.systemd1" +#define SSS_SYSTEMD_PATH "/org/freedesktop/systemd1" +#define SSS_SYSTEMD_UNIT "sssd.service" +#define SSS_SYSTEMD_MODE "replace" /* replace queued job if present */ + +typedef errno_t +(*systemd_method)(TALLOC_CTX *, struct sbus_sync_connection *, + const char *, const char *, const char *, const char *, + const char **); + +static errno_t sssctl_systemd_call(systemd_method method) +{ + TALLOC_CTX *tmp_ctx; + struct sbus_sync_connection *conn; + const char *job; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + conn = sbus_sync_connect_system(tmp_ctx, NULL); + if (conn == NULL) { + ERROR("Unable to connect to system bus!\n"); + ret = EIO; + goto done; + } + + ret = method(tmp_ctx, conn, SSS_SYSTEMD_BUS, + SSS_SYSTEMD_PATH, SSS_SYSTEMD_UNIT, + SSS_SYSTEMD_MODE, &job); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "systemd operation failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "New systemd job created: %s\n", job); + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t sssctl_systemd_start(void) +{ + DEBUG(SSSDBG_TRACE_FUNC, "Starting SSSD via systemd...\n"); + + return sssctl_systemd_call(sbus_call_systemd_StartUnit); +} + +errno_t sssctl_systemd_stop(void) +{ + DEBUG(SSSDBG_TRACE_FUNC, "Stopping SSSD via systemd...\n"); + + return sssctl_systemd_call(sbus_call_systemd_StopUnit); +} + +errno_t sssctl_systemd_restart(void) +{ + DEBUG(SSSDBG_TRACE_FUNC, "Restarting SSSD via systemd...\n"); + + return sssctl_systemd_call(sbus_call_systemd_RestartUnit); +} diff --git a/src/tools/sssctl/sssctl_user_checks.c b/src/tools/sssctl/sssctl_user_checks.c new file mode 100644 index 0000000..41cf765 --- /dev/null +++ b/src/tools/sssctl/sssctl_user_checks.c @@ -0,0 +1,321 @@ +/* + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <dlfcn.h> +#include <sys/types.h> +#include <pwd.h> +#include <nss.h> +#include <errno.h> +#include <inttypes.h> + +#include <security/pam_appl.h> + +#include "util/util.h" +#include "tools/common/sss_tools.h" +#include "tools/sssctl/sssctl.h" +#include "responder/ifp/ifp_iface/ifp_iface_sync.h" + +#ifdef HAVE_SECURITY_PAM_MISC_H +# include <security/pam_misc.h> +#elif defined(HAVE_SECURITY_OPENPAM_H) +# include <security/openpam.h> +#endif + +#ifdef HAVE_SECURITY_PAM_MISC_H +static struct pam_conv conv = { + misc_conv, + NULL +}; +#elif defined(HAVE_SECURITY_OPENPAM_H) +static struct pam_conv conv = { + openpam_ttyconv, + NULL +}; +#else +# error "Missing text based pam conversation function" +#endif + +#define DEFAULT_ACTION "acct" +#define DEFAULT_SERVICE "system-auth" + +#define DEFAULT_BUFSIZE 4096 + +#define PRINT_IFP_PROPERTY(all, name, fmt) do { \ + if (all->name.is_set) { \ + fprintf(stdout, " - %s: %" fmt "\n", #name, user->name.value); \ + } else { \ + fprintf(stdout, " - %s: not set\n", #name); \ + } \ +} while (0) + +static errno_t get_ifp_user(const char *username) +{ + TALLOC_CTX *tmp_ctx; + struct sbus_sync_connection *conn; + struct sbus_all_ifp_user *user; + const char *path; + struct hash_iter_context_t *extra_iter; + char **extra_values; + hash_entry_t *extra_entry; + int extra_idx; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + conn = sbus_sync_connect_system(tmp_ctx, NULL); + if (conn == NULL) { + ERROR("Unable to connect to system bus!\n"); + ret = EIO; + goto done; + } + + ret = sbus_call_ifp_users_FindByName(tmp_ctx, conn, IFP_BUS, IFP_PATH_USERS, + username, &path); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to find user by name [%d]: %s\n", + ret, sss_strerror(ret)); + PRINT_IFP_WARNING(ret); + goto done; + } + + ret = sbus_getall_ifp_user(tmp_ctx, conn, IFP_BUS, path, &user); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get user properties [%d]: %s\n", + ret, sss_strerror(ret)); + PRINT_IFP_WARNING(ret); + goto done; + } + + PRINT("SSSD InfoPipe user lookup result:\n"); + PRINT_IFP_PROPERTY(user, name, "s"); + PRINT_IFP_PROPERTY(user, uidNumber, PRIu32); + PRINT_IFP_PROPERTY(user, gidNumber, PRIu32); + PRINT_IFP_PROPERTY(user, gecos, "s"); + PRINT_IFP_PROPERTY(user, homeDirectory, "s"); + PRINT_IFP_PROPERTY(user, loginShell, "s"); + + /* print extra attributes */ + if (user->extraAttributes.is_set) { + extra_iter = new_hash_iter_context(user->extraAttributes.value); + if (extra_iter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "new_hash_iter_context failed.\n"); + ret = EINVAL; + goto done; + } + + while ((extra_entry = extra_iter->next(extra_iter)) != NULL) { + extra_values = extra_entry->value.ptr; + for(extra_idx = 0; extra_values[extra_idx] != NULL; ++extra_idx) { + fprintf(stdout, " - %s: %s\n", extra_entry->key.str, extra_values[extra_idx]); + } + } + } + + fprintf(stdout, "\n"); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static int sss_getpwnam_check(const char *user) +{ + void *dl_handle = NULL; + enum nss_status (*sss_getpwnam_r)(const char *name, struct passwd *result, + char *buffer, size_t buflen, + int *errnop); + struct passwd pwd = { 0 }; + enum nss_status status; + char *buffer = NULL; + size_t buflen; + int nss_errno; + int ret; + + dl_handle = dlopen("libnss_sss.so.2", RTLD_NOW); + if (dl_handle == NULL) { + ERROR("dlopen failed with [%s].\n", dlerror()); + ret = EIO; + goto done; + } + + sss_getpwnam_r = dlsym(dl_handle, "_nss_sss_getpwnam_r"); + if (sss_getpwnam_r == NULL) { + ERROR("dlsym failed with [%s].\n", dlerror()); + ret = EIO; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = malloc(buflen); + if (buffer == NULL) { + ERROR("malloc failed.\n"); + ret = ENOMEM; + goto done; + } + + status = sss_getpwnam_r(user, &pwd, buffer, buflen, &nss_errno); + if (status != NSS_STATUS_SUCCESS) { + ERROR("sss_getpwnam_r failed with [%d].\n", status); + ret = EIO; + goto done; + } + + PRINT("SSSD nss user lookup result:\n"); + PRINT(" - user name: %s\n", pwd.pw_name); + PRINT(" - user id: %d\n", pwd.pw_uid); + PRINT(" - group id: %d\n", pwd.pw_gid); + PRINT(" - gecos: %s\n", pwd.pw_gecos); + PRINT(" - home directory: %s\n", pwd.pw_dir); + PRINT(" - shell: %s\n\n", pwd.pw_shell); + + ret = 0; + +done: + if (dl_handle != NULL) { + dlclose(dl_handle); + } + + free(buffer); + + return ret; +} + +errno_t sssctl_user_checks(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + + pam_handle_t *pamh; + const char *user = NULL; + const char *action = DEFAULT_ACTION; + const char *service = DEFAULT_SERVICE; + int ret; + int pret; + const char *pam_user = NULL; + size_t c; + char **pam_env; + + /* Parse command line. */ + struct poptOption options[] = { + { "action", 'a', POPT_ARG_STRING, &action, 0, + _("PAM action [auth|acct|setc|chau|open|clos], default: " + DEFAULT_ACTION), NULL }, + { "service", 's', POPT_ARG_STRING, &service, 0, + _("PAM service, default: " DEFAULT_SERVICE), NULL }, + POPT_TABLEEND + }; + + ret = sss_tool_popt_ex(cmdline, options, SSS_TOOL_OPT_OPTIONAL, + NULL, NULL, "USERNAME", _("Specify user name."), + SSS_TOOL_OPT_REQUIRED, &user, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + goto done; + } + + PRINT("user: %s\naction: %s\nservice: %s\n\n", user, action, service); + + if (*user != '\0') { + ret = sss_getpwnam_check(user); + if (ret != 0) { + ERROR("User name lookup with [%s] failed.\n", user); + } + + ret = get_ifp_user(user); + if (ret != 0) { + ERROR("InfoPipe User lookup with [%s] failed.\n", user); + } + } + + ret = pam_start(service, user, &conv, &pamh); + if (ret != PAM_SUCCESS) { + ERROR("pam_start failed: %s\n", pam_strerror(pamh, ret)); + ret = EPERM; + goto done; + } + + if ( strncmp(action, "auth", 4)== 0 ) { + PRINT("testing pam_authenticate\n\n"); + ret = pam_authenticate(pamh, 0); + pret = pam_get_item(pamh, PAM_USER, (const void **) &pam_user); + if (pret != PAM_SUCCESS) { + ERROR("pam_get_item failed: %s\n", pam_strerror(pamh, pret)); + pam_user = "- not available -"; + } + ERROR("pam_authenticate for user [%s]: %s\n\n", pam_user, + pam_strerror(pamh, ret)); + } else if ( strncmp(action, "chau", 4)== 0 ) { + PRINT("testing pam_chauthtok\n\n"); + ret = pam_chauthtok(pamh, 0); + ERROR("pam_chauthtok: %s\n\n", pam_strerror(pamh, ret)); + } else if ( strncmp(action, "acct", 4)== 0 ) { + PRINT("testing pam_acct_mgmt\n\n"); + ret = pam_acct_mgmt(pamh, 0); + ERROR("pam_acct_mgmt: %s\n\n", pam_strerror(pamh, ret)); + } else if ( strncmp(action, "setc", 4)== 0 ) { + PRINT("testing pam_setcred\n\n"); + ret = pam_setcred(pamh, 0); + ERROR("pam_setcred: [%s]\n\n", pam_strerror(pamh, ret)); + } else if ( strncmp(action, "open", 4)== 0 ) { + PRINT("testing pam_open_session\n\n"); + ret = pam_open_session(pamh, 0); + ERROR("pam_open_session: %s\n\n", pam_strerror(pamh, ret)); + } else if ( strncmp(action, "clos", 4)== 0 ) { + PRINT("testing pam_close_session\n\n"); + ret = pam_close_session(pamh, 0); + ERROR("pam_close_session: %s\n\n", pam_strerror(pamh, ret)); + } else { + ERROR("unknown action\n"); + } + + ERROR("PAM Environment:\n"); + pam_env = pam_getenvlist(pamh); + if (pam_env != NULL && pam_env[0] != NULL) { + for (c = 0; pam_env[c] != NULL; c++) { + fprintf(stderr, " - %s\n", pam_env[c]); + free(pam_env[c]); + } + } else { + ERROR(" - no env -\n"); + } + free(pam_env); + + pam_end(pamh, ret); + ret = EOK; + +done: + free(discard_const(user)); + + return ret; +} diff --git a/src/tools/sssd_check_socket_activated_responders.c b/src/tools/sssd_check_socket_activated_responders.c new file mode 100644 index 0000000..dbd2331 --- /dev/null +++ b/src/tools/sssd_check_socket_activated_responders.c @@ -0,0 +1,147 @@ +/* + Authors: + Fabiano Fidêncio <fidencio@redhat.com> + + Copyright (C) 2017 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <popt.h> +#include <stdio.h> + +#include "util/util.h" +#include "util/sss_ini.h" +#include "confdb/confdb.h" + +static errno_t check_socket_activated_responder(const char *responder) +{ + errno_t ret; + const char *services; + const char *str; + TALLOC_CTX *tmp_ctx; + struct sss_ini *init_data; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + init_data = sss_ini_new(tmp_ctx); + if (init_data == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_ini_read_sssd_conf(init_data, + SSSD_CONFIG_FILE, + CONFDB_DEFAULT_CONFIG_DIR); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read configuration: [%d] [%s]", + ret, + sss_strerror(ret)); + goto done; + } + + ret = sss_ini_get_cfgobj(init_data, "sssd", "services"); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_ini_get_cfgobj() failed [%d].\n", ret); + goto done; + } + + ret = sss_ini_check_config_obj(init_data); + if (ret == ENOENT) { + /* In case there's no services' line at all, just return EOK. */ + ret = EOK; + goto done; + } + + services = sss_ini_get_string_config_value(init_data, &ret); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_ini_get_string_config_value() failed [%d]\n", + ret); + goto done; + } + + str = strstr(services, responder); + if (str != NULL) { + ret = EEXIST; + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +int main(int argc, const char *argv[]) +{ + int ret; + int opt; + poptContext pc; + char *responder = NULL; + + struct poptOption long_options[] = { + POPT_AUTOHELP + {"responders", 'r', POPT_ARG_STRING, &responder, 0, + _("The name of the responder to be checked"), NULL}, + POPT_TABLEEND + }; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while ((opt = poptGetNextOpt(pc)) != -1) { + switch (opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + ret = 1; + goto done; + } + } + + if (responder == NULL) { + poptPrintUsage(pc, stderr, 0); + ret = 1; + goto done; + } + + ret = check_socket_activated_responder(responder); + if (ret != EOK) { + DEBUG(SSSDBG_DEFAULT, + "Misconfiguration found for the %s responder.\n" + "The %s responder has been configured to be socket-activated " + "but it's still mentioned in the services' line in %s.\n" + "Please, consider either adjusting your services' line in %s " + "or disabling the %s's socket by calling:\n" + "\"systemctl disable sssd-%s.socket\"", + responder, responder, SSSD_CONFIG_FILE, SSSD_CONFIG_FILE, + responder, responder); + goto done; + } + + ret = EOK; +done: + poptFreeContext(pc); + return ret; +} diff --git a/src/tools/tools_mc_util.c b/src/tools/tools_mc_util.c new file mode 100644 index 0000000..a4f3a81 --- /dev/null +++ b/src/tools/tools_mc_util.c @@ -0,0 +1,237 @@ +/* + SSSD + + tools_mc_util - interface to the memcache for userspace tools + + Copyright (C) Red Hat 2013 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <talloc.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <signal.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "util/mmap_cache.h" +#include "util/sss_cli_cmd.h" +#include "sss_client/sss_cli.h" +#include "tools/common/sss_process.h" + +/* This is a copy of sss_mc_set_recycled present in + * src/responder/nss/nsssrv_mmap_cache.c. If you modify this function, + * you should modify the original function too. */ +static errno_t sss_mc_set_recycled(int fd) +{ + uint32_t w = SSS_MC_HEADER_RECYCLED; + off_t offset; + off_t pos; + ssize_t written; + + offset = offsetof(struct sss_mc_header, status); + + pos = lseek(fd, offset, SEEK_SET); + if (pos == -1) { + /* What do we do now? */ + return errno; + } + + errno = 0; + written = sss_atomic_write_s(fd, (uint8_t *)&w, sizeof(w)); + if (written == -1) { + return errno; + } + + if (written != sizeof(w)) { + /* Write error */ + return EIO; + } + + return EOK; +} + +static errno_t sss_memcache_invalidate(const char *mc_filename) +{ + int mc_fd = -1; + errno_t ret; + errno_t pret; + useconds_t t = 50000; + int retries = 2; + + if (!mc_filename) { + return EINVAL; + } + + mc_fd = open(mc_filename, O_RDWR); + if (mc_fd == -1) { + ret = errno; + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC,"Memory cache file %s " + "does not exist.\n", mc_filename); + return EOK; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to open file %s: %s\n", + mc_filename, strerror(ret)); + return ret; + } + } + + ret = sss_br_lock_file(mc_fd, 0, 1, retries, t); + if (ret == EACCES) { + DEBUG(SSSDBG_TRACE_FUNC, + "File %s already locked by someone else.\n", mc_filename); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to lock file %s.\n", mc_filename); + goto done; + } + /* Mark the mc file as recycled. */ + ret = sss_mc_set_recycled(mc_fd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to mark memory cache file %s " + "as recycled.\n", mc_filename); + goto done; + } + + ret = EOK; +done: + if (mc_fd != -1) { + /* Closing the file also releases the lock */ + close(mc_fd); + + /* Only unlink the file if invalidation was successful */ + if (ret == EOK) { + pret = unlink(mc_filename); + if (pret == -1) { + pret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to unlink file %s, %d [%s]. " + "Will be unlinked later by sssd_nss.\n", + mc_filename, pret, strerror(pret)); + } + } + } + return ret; +} + +static int clear_memcache(bool *sssd_nss_is_off) +{ + int ret; + ret = sss_memcache_invalidate(SSS_NSS_MCACHE_DIR"/passwd"); + if (ret != EOK) { + if (ret == EACCES) { + *sssd_nss_is_off = false; + return EOK; + } else { + return ret; + } + } + + ret = sss_memcache_invalidate(SSS_NSS_MCACHE_DIR"/group"); + if (ret != EOK) { + if (ret == EACCES) { + *sssd_nss_is_off = false; + return EOK; + } else { + return ret; + } + } + + ret = sss_memcache_invalidate(SSS_NSS_MCACHE_DIR"/initgroups"); + if (ret != EOK) { + if (ret == EACCES) { + *sssd_nss_is_off = false; + return EOK; + } else { + return ret; + } + } + + *sssd_nss_is_off = true; + return EOK; +} + +static errno_t wait_till_nss_responder_invalidate_cache(void) +{ + struct stat stat_buf = { 0 }; + const time_t max_wait = 1000000; /* 1 second */ + const __useconds_t step_time = 5000; /* 5 milliseconds */ + const size_t steps_count = max_wait / step_time; + int ret; + + for (size_t i = 0; i < steps_count; ++i) { + ret = stat(SSS_NSS_MCACHE_DIR "/" CLEAR_MC_FLAG, &stat_buf); + if (ret == -1) { + ret = errno; + if (ret == ENOENT) { + /* nss responder has already invalidated memory caches */ + return EOK; + } + + DEBUG(SSSDBG_CRIT_FAILURE, + "stat failed: %s (%d)\n", sss_strerror(ret), ret); + } + + usleep(step_time); + } + + return EAGAIN; +} + +errno_t sss_memcache_clear_all(void) +{ + errno_t ret; + bool sssd_nss_is_off = false; + FILE *clear_mc_flag; + + ret = clear_memcache(&sssd_nss_is_off); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to clear caches.\n"); + return EIO; + } + if (!sssd_nss_is_off) { + /* sssd_nss is running -> signal monitor to invalidate memcache */ + clear_mc_flag = fopen(SSS_NSS_MCACHE_DIR"/"CLEAR_MC_FLAG, "w"); + if (clear_mc_flag == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to create clear_mc_flag file. " + "Memory cache will not be cleared.\n"); + return EIO; + } + ret = fclose(clear_mc_flag); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to close file descriptor: %s\n", + strerror(ret)); + return EIO; + } + DEBUG(SSSDBG_TRACE_FUNC, "Sending SIGHUP to monitor.\n"); + ret = sss_signal(SIGHUP); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to send SIGHUP to monitor.\n"); + return EIO; + } + + ret = wait_till_nss_responder_invalidate_cache(); + if (ret != EOK) { + ERROR("The memcache was not invalidated by NSS responder.\n"); + } + } + + return EOK; +} diff --git a/src/tools/tools_util.c b/src/tools/tools_util.c new file mode 100644 index 0000000..eabc112 --- /dev/null +++ b/src/tools/tools_util.c @@ -0,0 +1,72 @@ +/* + SSSD + + tools_utils.c + + Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <popt.h> +#include <errno.h> + +#include "util/util.h" +#include "tools/tools_util.h" + +/* + * Print poptUsage as well as our error message + */ +void usage(poptContext pc, const char *error) +{ + size_t lentmp; + + poptPrintUsage(pc, stderr, 0); + + if (error) { + lentmp = strlen(error); + if ((lentmp > 0) && (error[lentmp - 1] != '\n')) { + fprintf(stderr, "%s\n", error); + return; + } + + fprintf(stderr, "%s", error); + } +} + +int set_locale(void) +{ + char *c; + + c = setlocale(LC_ALL, ""); + if (c == NULL) { + /* If setlocale fails, continue with the default + * locale. */ + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to set locale\n"); + } + + errno = 0; + c = bindtextdomain(PACKAGE, LOCALEDIR); + if (c == NULL) { + return errno; + } + + errno = 0; + c = textdomain(PACKAGE); + if (c == NULL) { + return errno; + } + + return EOK; +} diff --git a/src/tools/tools_util.h b/src/tools/tools_util.h new file mode 100644 index 0000000..a6f71b3 --- /dev/null +++ b/src/tools/tools_util.h @@ -0,0 +1,51 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#ifndef __TOOLS_UTIL_H__ +#define __TOOLS_UTIL_H__ + +#define BAD_POPT_PARAMS(pc, msg, val, label) do { \ + usage(pc, msg); \ + val = EXIT_FAILURE; \ + goto label; \ +} while(0) + +#define CHECK_ROOT(val, prg_name) do { \ + val = getuid(); \ + if (val != 0) { \ + DEBUG(SSSDBG_CRIT_FAILURE, "Running under %d, must be root\n", val); \ + ERROR("%1$s must be run as root\n", prg_name); \ + val = EXIT_FAILURE; \ + goto fini; \ + } \ +} while(0) + +void usage(poptContext pc, const char *error); + +int set_locale(void); + +errno_t sss_signal(int signum); + +/* tools_mc_util.c */ +errno_t sss_memcache_clear_all(void); + +#endif /* __TOOLS_UTIL_H__ */ diff --git a/src/tools/wrappers/sss_debuglevel.in b/src/tools/wrappers/sss_debuglevel.in new file mode 100644 index 0000000..a55afcd --- /dev/null +++ b/src/tools/wrappers/sss_debuglevel.in @@ -0,0 +1,4 @@ +#!/bin/sh +sbindir=@sbindir@ +echo "Redirecting to $sbindir/sssctl debug-level" >&2 +exec $sbindir/sssctl debug-level "$@" |