diff options
Diffstat (limited to 'ipa-client')
25 files changed, 3051 insertions, 0 deletions
diff --git a/ipa-client/AUTHORS b/ipa-client/AUTHORS new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ipa-client/AUTHORS diff --git a/ipa-client/Makefile.am b/ipa-client/Makefile.am new file mode 100644 index 00000000..796a923f --- /dev/null +++ b/ipa-client/Makefile.am @@ -0,0 +1,100 @@ +# This file will be processed with automake-1.7 to create Makefile.in + +AUTOMAKE_OPTIONS = 1.7 + +NULL = + +INCLUDES = \ + -I. \ + -I$(srcdir) \ + -DPREFIX=\""$(prefix)"\" \ + -DBINDIR=\""$(bindir)"\" \ + -DLIBDIR=\""$(libdir)"\" \ + -DLIBEXECDIR=\""$(libexecdir)"\" \ + -DDATADIR=\""$(datadir)"\" \ + $(KRB5_CFLAGS) \ + $(OPENLDAP_CFLAGS) \ + $(MOZLDAP_CFLAGS) \ + $(SASL_CFLAGS) \ + $(POPT_CFLAGS) \ + $(WARN_CFLAGS) \ + $(NULL) + +sbin_PROGRAMS = \ + ipa-getkeytab \ + $(NULL) + +ipa_getkeytab_SOURCES = \ + ipa-getkeytab.c \ + $(NULL) + +ipa_getkeytab_LDADD = \ + $(KRB5_LIBS) \ + $(OPENLDAP_LIBS) \ + $(MOZLDAP_LIBS) \ + $(SASL_LIBS) \ + $(POPT_LIBS) \ + $(NULL) + +SUBDIRS = \ + firefox \ + ipaclient \ + ipa-install \ + man \ + $(NULL) + +EXTRA_DIST = \ + ipa-client.spec \ + COPYING \ + AUTHORS \ + INSTALL \ + README \ + HACKING \ + NEWS \ + ChangeLog \ + $(NULL) + +DISTCLEANFILES = \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + intltool-*.in \ + compile \ + configure \ + COPYING \ + INSTALL \ + install-sh \ + missing \ + mkinstalldirs \ + config.guess \ + ltmain.sh \ + config.sub \ + depcomp \ + Makefile.in \ + config.h.* \ + aclocal.m4 \ + version.m4 \ + ipa-client.spec \ + py-compile \ + $(NULL) + +# Creating ChangeLog from hg log (taken from cairo/Makefile.am): + +ChangeLog: $(srcdir)/ChangeLog + +$(srcdir)/ChangeLog: + @if test -d "$(srcdir)/../.hg"; then \ + (cd "$(srcdir)" && \ + ./missing --run hg log --verbose) | fmt --split-only > $@.tmp \ + && mv -f $@.tmp $@ \ + || ($(RM) $@.tmp; \ + echo Failed to generate ChangeLog, your ChangeLog may be outdated >&2; \ + (test -f $@ || echo hg log is required to generate this file >> $@)); \ + else \ + test -f $@ || \ + (echo A hg checkout and hg -log is required to generate ChangeLog >&2 && \ + echo A hg checkout and hg log is required to generate this file >> $@); \ + fi + +.PHONY: ChangeLog $(srcdir)/ChangeLog diff --git a/ipa-client/NEWS b/ipa-client/NEWS new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ipa-client/NEWS diff --git a/ipa-client/README b/ipa-client/README new file mode 100644 index 00000000..d7c30f0c --- /dev/null +++ b/ipa-client/README @@ -0,0 +1,24 @@ +Code to be installed on any client that wants to be in an IPA domain. + +Mostly consists of a tool for Linux systems that will help configure the +client so it will work properly in a kerberized environment. + +It also includes several ways to configure Firefox to do single sign-on. + +The two methods on the client side are: + +1. globalsetup.sh. This modifies the global Firefox installation so that + any profiles created will be pre-configured. + +2. usersetup.sh. This will update a user's existing profile. + +The downside of #1 is that an rpm -V will return a failure. It will also +need to be run with every update of Firefox. + +One a profile contains the proper preferences it will be unaffected by +upgrades to Firefox. + +The downside of #2 is that every user would need to run this each time they +create a new profile. + +There is a third, server-side method. See ipa-server/README for details. diff --git a/ipa-client/autogen.sh b/ipa-client/autogen.sh new file mode 100755 index 00000000..014487e4 --- /dev/null +++ b/ipa-client/autogen.sh @@ -0,0 +1,196 @@ +#!/bin/sh +# Run this to generate all the initial makefiles, etc. +set -e + +PACKAGE=freeipa-client + +LIBTOOLIZE=${LIBTOOLIZE-libtoolize} +LIBTOOLIZE_FLAGS="--copy --force" +AUTOHEADER=${AUTOHEADER-autoheader} +AUTOMAKE_FLAGS="--add-missing --gnu" +AUTOCONF=${AUTOCONF-autoconf} + +# automake 1.8 requires autoconf 2.58 +# automake 1.7 requires autoconf 2.54 +automake_min_vers=1.7 +aclocal_min_vers=$automake_min_vers +autoconf_min_vers=2.54 +libtoolize_min_vers=1.4 + +# The awk-based string->number conversion we use needs a C locale to work +# as expected. Setting LC_ALL overrides whether the user set LC_ALL, +# LC_NUMERIC, or LANG. +LC_ALL=C + +ARGV0=$0 + +# Allow invocation from a separate build directory; in that case, we change +# to the source directory to run the auto*, then change back before running configure +srcdir=`dirname $ARGV0` +test -z "$srcdir" && srcdir=. + +ORIGDIR=`pwd` + +cd $srcdir + +# Usage: +# compare_versions MIN_VERSION ACTUAL_VERSION +# returns true if ACTUAL_VERSION >= MIN_VERSION +compare_versions() { + ch_min_version=$1 + ch_actual_version=$2 + ch_status=0 + IFS="${IFS= }"; ch_save_IFS="$IFS"; IFS="." + set $ch_actual_version + for ch_min in $ch_min_version; do + ch_cur=`echo $1 | sed 's/[^0-9].*$//'`; shift # remove letter suffixes + if [ -z "$ch_min" ]; then break; fi + if [ -z "$ch_cur" ]; then ch_status=1; break; fi + if [ $ch_cur -gt $ch_min ]; then break; fi + if [ $ch_cur -lt $ch_min ]; then ch_status=1; break; fi + done + IFS="$ch_save_IFS" + return $ch_status +} + +if ($AUTOCONF --version) < /dev/null > /dev/null 2>&1 ; then + if ($AUTOCONF --version | head -n 1 | awk 'NR==1 { if( $(NF) >= '$autoconf_min_vers') \ + exit 1; exit 0; }'); + then + echo "$ARGV0: ERROR: \`$AUTOCONF' is too old." + $AUTOCONF --version + echo " (version $autoconf_min_vers or newer is required)" + DIE="yes" + fi +else + echo $AUTOCONF: command not found + echo + echo "$ARGV0: ERROR: You must have \`autoconf' installed to compile $PACKAGE." + echo " (version $autoconf_min_vers or newer is required)" + DIE="yes" +fi + +# +# Hunt for an appropriate version of automake and aclocal; we can't +# assume that 'automake' is necessarily the most recent installed version +# +# We check automake first to allow it to be a newer version than we know about. +# +if test x"$AUTOMAKE" = x || test x"$ACLOCAL" = x ; then + am_ver="" + for ver in "" "-1.9" "-1.8" "-1.7" ; do + am="automake$ver" + if ($am --version) < /dev/null > /dev/null 2>&1 ; then + if ($am --version | head -n 1 | awk 'NR==1 { if( $(NF) >= '$automake_min_vers') \ + exit 1; exit 0; }'); then : ; else + am_ver=$ver + break; + fi + fi + done + + AUTOMAKE=${AUTOMAKE-automake$am_ver} + ACLOCAL=${ACLOCAL-aclocal$am_ver} +fi + +# +# Now repeat the tests with the copies we decided upon and error out if they +# aren't sufficiently new. +# +if ($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 ; then + automake_actual_version=`$AUTOMAKE --version | head -n 1 | \ + sed 's/^.*[ ]\([0-9.]*[a-z]*\).*$/\1/'` + if ! compare_versions $automake_min_vers $automake_actual_version; then + echo "$ARGV0: ERROR: \`$AUTOMAKE' is too old." + $AUTOMAKE --version + echo " (version $automake_min_vers or newer is required)" + DIE="yes" + fi + if ($ACLOCAL --version) < /dev/null > /dev/null 2>&1; then + aclocal_actual_version=`$ACLOCAL --version | head -n 1 | \ + sed 's/^.*[ ]\([0-9.]*[a-z]*\).*$/\1/'` + + if ! compare_versions $aclocal_min_vers $aclocal_actual_version; then + echo "$ARGV0: ERROR: \`$ACLOCAL' is too old." + $ACLOCAL --version + echo " (version $aclocal_min_vers or newer is required)" + DIE="yes" + fi + else + echo $ACLOCAL: command not found + echo + echo "$ARGV0: ERROR: Missing \`$ACLOCAL'" + echo " The version of $AUTOMAKE installed doesn't appear recent enough." + DIE="yes" + fi +else + echo $AUTOMAKE: command not found + echo + echo "$ARGV0: ERROR: You must have \`automake' installed to compile $PACKAGE." + echo " (version $automake_min_vers or newer is required)" + DIE="yes" +fi + +if ($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 ; then + if ($LIBTOOLIZE --version | awk 'NR==1 { if( $4 >= '$libtoolize_min_vers') \ + exit 1; exit 0; }'); + then + echo "$ARGV0: ERROR: \`$LIBTOOLIZE' is too old." + echo " (version $libtoolize_min_vers or newer is required)" + DIE="yes" + fi +else + echo $LIBTOOLIZE: command not found + echo + echo "$ARGV0: ERROR: You must have \`libtoolize' installed to compile $PACKAGE." + echo " (version $libtoolize_min_vers or newer is required)" + DIE="yes" +fi + +if test -z "$ACLOCAL_FLAGS"; then + acdir=`$ACLOCAL --print-ac-dir` + if [ ! -f $acdir/pkg.m4 ]; then + echo "$ARGV0: Error: Could not find pkg-config macros." + echo " (Looked in $acdir/pkg.m4)" + echo " If pkg.m4 is available in /another/directory, please set" + echo " ACLOCAL_FLAGS=\"-I /another/directory\"" + echo " Otherwise, please install pkg-config." + echo "" + echo "pkg-config is available from:" + echo "http://www.freedesktop.org/software/pkgconfig/" + DIE=yes + fi +fi + +if test "X$DIE" != X; then + exit 1 +fi + + +if test -z "$*"; then + echo "$ARGV0: Note: \`./configure' will be run with no arguments." + echo " If you wish to pass any to it, please specify them on the" + echo " \`$0' command line." + echo +fi + +do_cmd() { + echo "$ARGV0: running \`$@'" + $@ +} + +do_cmd $LIBTOOLIZE $LIBTOOLIZE_FLAGS + +do_cmd $ACLOCAL $ACLOCAL_FLAGS + +do_cmd $AUTOHEADER + +do_cmd $AUTOMAKE $AUTOMAKE_FLAGS + +do_cmd $AUTOCONF + +cd $ORIGDIR || exit 1 + +rm -f config.cache + +do_cmd $srcdir/configure --cache-file=config.cache --disable-static --enable-maintainer-mode --enable-gtk-doc ${1+"$@"} && echo "Now type \`make' to compile" || exit 1 diff --git a/ipa-client/configure.ac b/ipa-client/configure.ac new file mode 100644 index 00000000..b639e1d1 --- /dev/null +++ b/ipa-client/configure.ac @@ -0,0 +1,195 @@ +AC_PREREQ(2.59) +m4_include(version.m4) +AC_INIT([ipa-client], + IPA_VERSION, + [https://hosted.fedoraproject.org/projects/freeipa/newticket]) + +AC_CONFIG_SRCDIR([ipaclient/__init__.py]) +AC_CONFIG_HEADERS([config.h]) + +AM_INIT_AUTOMAKE + +AM_MAINTAINER_MODE + +AC_PROG_CC +AC_STDC_HEADERS +AC_DISABLE_STATIC +AC_PROG_LIBTOOL + +AC_HEADER_STDC + +AC_SUBST(VERSION) + +dnl --------------------------------------------------------------------------- +dnl - Check for KRB5 +dnl --------------------------------------------------------------------------- + +KRB5_LIBS= +AC_CHECK_HEADER(krb5.h) + +krb5_impl=mit + +if test "x$ac_cv_header_krb5_h" = "xyes" ; then + dnl lazy check for Heimdal Kerberos + AC_CHECK_HEADERS(heim_err.h) + if test $ac_cv_header_heim_err_h = yes ; then + krb5_impl=heimdal + else + krb5_impl=mit + fi + + if test "x$krb5_impl" = "xmit"; then + AC_CHECK_LIB(k5crypto, main, + [krb5crypto=k5crypto], + [krb5crypto=crypto]) + + AC_CHECK_LIB(krb5, main, + [have_krb5=yes + KRB5_LIBS="-lkrb5 -l$krb5crypto -lcom_err"], + [have_krb5=no], + [-l$krb5crypto -lcom_err]) + + elif test "x$krb5_impl" = "xheimdal"; then + AC_CHECK_LIB(des, main, + [krb5crypto=des], + [krb5crypto=crypto]) + + AC_CHECK_LIB(krb5, main, + [have_krb5=yes + KRB5_LIBS="-lkrb5 -l$krb5crypto -lasn1 -lroken -lcom_err"], + [have_krb5=no], + [-l$krb5crypto -lasn1 -lroken -lcom_err]) + + AC_DEFINE(HAVE_HEIMDAL_KERBEROS, 1, + [define if you have HEIMDAL Kerberos]) + + else + have_krb5=no + AC_MSG_WARN([Unrecognized Kerberos5 Implementation]) + fi + + if test "x$have_krb5" = "xyes" ; then + ol_link_krb5=yes + + AC_DEFINE(HAVE_KRB5, 1, + [define if you have Kerberos V]) + + else + AC_MSG_ERROR([Required Kerberos 5 support not available]) + fi + +fi + +AC_SUBST(KRB5_LIBS) + +dnl --------------------------------------------------------------------------- +dnl - Check for Mozilla LDAP or OpenLDAP SDK +dnl --------------------------------------------------------------------------- + +AC_ARG_WITH(openldap, [ --with-openldap Use OpenLDAP]) + +if test x$with_openldap = xyes; then + AC_CHECK_LIB(ldap, ldap_search, with_ldap=yes) + dnl Check for other libraries we need to link with to get the main routines. + test "$with_ldap" != "yes" && { AC_CHECK_LIB(ldap, ldap_open, [with_ldap=yes with_ldap_lber=yes], , -llber) } + test "$with_ldap" != "yes" && { AC_CHECK_LIB(ldap, ldap_open, [with_ldap=yes with_ldap_lber=yes with_ldap_krb=yes], , -llber -lkrb) } + test "$with_ldap" != "yes" && { AC_CHECK_LIB(ldap, ldap_open, [with_ldap=yes with_ldap_lber=yes with_ldap_krb=yes with_ldap_des=yes], , -llber -lkrb -ldes) } + dnl Recently, we need -lber even though the main routines are elsewhere, + dnl because otherwise be get link errors w.r.t. ber_pvt_opt_on. So just + dnl check for that (it's a variable not a fun but that doesn't seem to + dnl matter in these checks) and stick in -lber if so. Can't hurt (even to + dnl stick it in always shouldn't hurt, I don't think) ... #### Someone who + dnl #### understands LDAP needs to fix this properly. + test "$with_ldap_lber" != "yes" && { AC_CHECK_LIB(lber, ber_pvt_opt_on, with_ldap_lber=yes) } + + if test "$with_ldap" = "yes"; then + if test "$with_ldap_des" = "yes" ; then + OPENLDAP_LIBS="${OPENLDAP_LIBS} -ldes" + fi + if test "$with_ldap_krb" = "yes" ; then + OPENLDAP_LIBS="${OPENLDAP_LIBS} -lkrb" + fi + if test "$with_ldap_lber" = "yes" ; then + OPENLDAP_LIBS="${OPENLDAP_LIBS} -llber" + fi + OPENLDAP_LIBS="${OPENLDAP_LIBS} -lldap" + else + AC_MSG_ERROR([OpenLDAP not found]) + fi + + AC_SUBST(OPENLDAP_LIBS) +else + PKG_CHECK_MODULES(MOZLDAP, mozldap > 6) + MOZLDAP_CFLAGS="${MOZLDAP_CFLAGS} -DWITH_MOZLDAP" + AC_SUBST(MOZLDAP_CFLAGS) +fi + + +dnl --------------------------------------------------------------------------- +dnl - Check for POPT +dnl --------------------------------------------------------------------------- + +POPT_LIBS= +AC_CHECK_HEADER(popt.h) +AC_CHECK_LIB(popt, poptGetContext, [POPT_LIBS="-lpopt"]) +AC_SUBST(POPT_LIBS) + +dnl --------------------------------------------------------------------------- +dnl - Check for SASL +dnl --------------------------------------------------------------------------- + +SASL_LIBS= +AC_CHECK_HEADER(sasl/sasl.h) +AC_CHECK_LIB(sasl2, sasl_client_init, [SASL_LIBS="-lsasl2"]) +AC_SUBST(SASL_LIBS) + +dnl --------------------------------------------------------------------------- +dnl - Check for Python +dnl --------------------------------------------------------------------------- + +AC_MSG_NOTICE([Checking for Python]) +have_python=no +AM_PATH_PYTHON(2.3) + +if test "x$PYTHON" = "x" ; then + AC_MSG_ERROR([Python not found]) +fi + +dnl --------------------------------------------------------------------------- +dnl - Set the data install directory since we don't use pkgdatadir +dnl --------------------------------------------------------------------------- + +IPA_DATA_DIR="$datadir/ipa" +AC_SUBST(IPA_DATA_DIR) + +dnl --------------------------------------------------------------------------- +dnl Finish +dnl --------------------------------------------------------------------------- + +# Files + +AC_CONFIG_FILES([ + Makefile + firefox/Makefile + ipaclient/Makefile + ipa-install/Makefile + man/Makefile +]) + +AC_OUTPUT + +echo " + IPA client $VERSION + ======================== + + prefix: ${prefix} + exec_prefix: ${exec_prefix} + libdir: ${libdir} + bindir: ${bindir} + sbindir: ${sbindir} + sysconfdir: ${sysconfdir} + localstatedir: ${localstatedir} + datadir: ${datadir} + source code location: ${srcdir} + Maintainer mode: ${USE_MAINTAINER_MODE} +" diff --git a/ipa-client/firefox/Makefile.am b/ipa-client/firefox/Makefile.am new file mode 100644 index 00000000..daf69424 --- /dev/null +++ b/ipa-client/firefox/Makefile.am @@ -0,0 +1,18 @@ +NULL = + +appdir = $(IPA_DATA_DIR)/ipaclient +app_DATA = \ + ipa.cfg \ + ipa.js \ + $(NULL) + +EXTRA_DIST = \ + README \ + $(app_DATA) \ + globalsetup.sh \ + usersetup.sh \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in diff --git a/ipa-client/firefox/README b/ipa-client/firefox/README new file mode 100644 index 00000000..67013fcc --- /dev/null +++ b/ipa-client/firefox/README @@ -0,0 +1,23 @@ +Firefox automatic configuration. + +ipa.cfg needs to be installed in the Firefox root directory +(/usr/lib/firefox-version). It can be a symlink somewhere else. We install +the actual file into /usr/share/ipa. + +ipa.js contains the javascript that sets the desired configuration. + +The Firefox all.js needs to be modified to set: + +pref('general.config.obscure_value', 0); +pref('general.config.filename', 'ipa.cfg'); + +First need to remove any existing values for these. + +For more information on autoconfiguration, see: +http://mit.edu/~firefox/www/maintainers/autoconfig.html + +globalsetup.sh will change the default setup for all users and will +lock the preferences. The downside is that rpm -V will show firefox as +corrupt. + +usersetup.sh will change all existing profiles of the current user diff --git a/ipa-client/firefox/globalsetup.sh b/ipa-client/firefox/globalsetup.sh new file mode 100644 index 00000000..403c3d3e --- /dev/null +++ b/ipa-client/firefox/globalsetup.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +# 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; version 2 only +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +ipacfg="ipa.cfg" + +for file in /usr/lib/firefox-* /usr/lib64/firefox* +do + # Find the configuration file we want to change + cfg=`find $file -name all.js` + + # determine the directory by removing all.js + dir=`echo $cfg | sed 's/greprefs\/all.js//'` + + # It is possible that there will be empty Firefox directories, so skip + # those. + if test "X"$cfg != "X"; then + + rm -f $cfg.new + + # If the configuration already exists, remove it + if grep general.config.filename $cfg > /dev/null 2>&1; then + grep -v general.config.filename $cfg > $cfg.new + mv $cfg.new $cfg + fi + + # We have the configuration unobscured + if grep general.config.filename $cfg > /dev/null 2>&1; then + grep -v general.config.obscure_value $cfg > $cfg.new + mv $cfg.new $cfg + fi + + # Now we can add the new stuff to the file + echo "pref('general.config.obscure_value', 0);" >> "$cfg" + echo "pref('general.config.filename', '$ipacfg');" >> "$cfg" + + # Create a link to our configuration file + rm -f $dir/$ipacfg + ln -s /usr/share/ipa/ipa.cfg $dir/$ipacfg + fi +done diff --git a/ipa-client/firefox/ipa.cfg b/ipa-client/firefox/ipa.cfg new file mode 100644 index 00000000..448c350b --- /dev/null +++ b/ipa-client/firefox/ipa.cfg @@ -0,0 +1,19 @@ +# +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +lockPref("autoadmin.global_config_url","file:///usr/share/ipa/ipa.js"); diff --git a/ipa-client/firefox/ipa.js b/ipa-client/firefox/ipa.js new file mode 100644 index 00000000..a280954a --- /dev/null +++ b/ipa-client/firefox/ipa.js @@ -0,0 +1,34 @@ +/* + * 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; version 2 only + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +try +{ + /* Kerberos SSO configuration */ + lockPref("network.negotiate-auth.trusted-uris", ".freeipa.org"); + lockPref("network.negotiate-auth.delegation-uris", ".freeipa.org"); + + /* These are the defaults */ + lockPref("network.negotiate-auth.gsslib", ""); + lockPref("network.negotiate-auth.using-native-gsslib", true); + lockPref("network.negotiate-auth.allow-proxies", true); + + /* For Windows */ + lockPref("network.auth.use-sspi", false); +} +catch(e) +{ + displayError("Error in Autoconfig", e); +} diff --git a/ipa-client/firefox/usersetup.sh b/ipa-client/firefox/usersetup.sh new file mode 100644 index 00000000..e8d152b1 --- /dev/null +++ b/ipa-client/firefox/usersetup.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +# 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; version 2 only +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +for file in `find $HOME/.mozilla -name prefs.js` +do + rm -f $file.new + + # If the configuration already exists, remove it + if grep network.negotiate- $file > /dev/null 2>&1; then + grep -v network.negotiate- $file > $file.new + mv $file.new $file + fi + + # We have the configuration unobscured + if grep network.auth.use-sspi $file > /dev/null 2>&1; then + grep -v network.auth.use-sspi $file > $file.new + mv $file.new $file + fi + + # Now we can add the new stuff to the file + echo "user_pref('network.auth.use-sspi', false);" >> $file + echo "user_pref('network.cookie.prefsMigrated', true);" >> $file + echo "user_pref('network.negotiate-auth.allow-proxies', true);" >> $file + echo "user_pref('network.negotiate-auth.delegation-uris', '.freeipa.org');" >> $file + echo "user_pref('network.negotiate-auth.gsslib', '');" >> $file + echo "user_pref('network.negotiate-auth.trusted-uris', '.freeipa.org');" >> $file + echo "user_pref('network.negotiate-auth.using-native-gsslib', true);" >> $file +done diff --git a/ipa-client/ipa-client.spec.in b/ipa-client/ipa-client.spec.in new file mode 100644 index 00000000..686259ad --- /dev/null +++ b/ipa-client/ipa-client.spec.in @@ -0,0 +1,86 @@ +Name: ipa-client +Version: __VERSION__ +Release: __RELEASE__%{?dist} +Summary: IPA client + +Group: System Environment/Base +License: GPLv2 +URL: http://www.freeipa.org +Source0: %{name}-%{version}.tgz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +Requires: python python-ldap python-krbV ipa-python cyrus-sasl-gssapi + +%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} + +%description +IPA is a server for identity, policy, and audit. +The client package provide install and configuration scripts for clients. + +%prep +%setup -q +./configure --prefix=%{buildroot}/usr --libdir=%{buildroot}/%{_libdir} --sysconfdir=%{buildroot}/etc --mandir=%{buildroot}/%{_mandir} + +%build + +make + +%install +rm -rf %{buildroot} + +make install + +mkdir -p %{buildroot}/%{_localstatedir}/lib/ipa-client/sysrestore + + +%clean +rm -rf %{buildroot} + + +%files +%defattr(-,root,root,-) +%{_sbindir}/ipa-client-install +%{_sbindir}/ipa-getkeytab + +%dir %{_usr}/share/ipa +%{_usr}/share/ipa/* + +%dir %{python_sitelib}/ipaclient +%{python_sitelib}/ipaclient/*.py* + +%dir %{_localstatedir}/lib/ipa-client +%dir %{_localstatedir}/lib/ipa-client/sysrestore + +%{_mandir}/man1/* + +%changelog +* Thu Apr 3 2008 Rob Crittenden <rcritten@redhat.com> - 1.0.0-1 +- Version bump for release + +* Mon Feb 25 2008 Rob Crittenden <rcritten@redhat.com> - 0.99.0-2 +- Add ipa-getkeytab man page + +* Thu Feb 21 2008 Rob Crittenden <rcritten@redhat.com> - 0.99.0-1 +- Version bump for release + +* Thu Jan 31 2008 Rob Crittenden <rcritten@redhat.com> - 0.6.0-3 +- Marked with wrong license. IPA is GPLv2. + +* Thu Jan 17 2008 Rob Crittenden <rcritten@redhat.com> - 0.6.0-2 +- Fixed License in specfile +- Move client python files to /usr/lib*/python*/site-packages + +* Fri Dec 21 2007 Karl MacMillan <kmacmill@redhat.com> - 0.6.0-1 +- Version bump for release + +* Wed Nov 21 2007 Karl MacMillan <kmacmill@redhat.com> - 0.5.0-1 +- Version bump for release and rpm name change + +* Thu Nov 1 2007 Karl MacMillan <kmacmill@redhat.com> - 0.3.1-1 +- Version bump for release + +* Thu Oct 18 2007 Karl MacMillan <kmacmill@redhat.com> - 0.3.0-2 +- Convert to autotools-based build + +* Thu Aug 16 2007 Simo Sorce <ssorce@redhat.com> - 0.1.0-1 +- Initial rpm version diff --git a/ipa-client/ipa-getkeytab.c b/ipa-client/ipa-getkeytab.c new file mode 100644 index 00000000..fbeb547a --- /dev/null +++ b/ipa-client/ipa-getkeytab.c @@ -0,0 +1,836 @@ +/* Authors: Simo Sorce <ssorce@redhat.com> + * + * Copyright (C) 2007 Red Hat + * see file 'COPYING' for use and warranty information + * + * 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; version 2 only + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define _GNU_SOURCE + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/time.h> +#include <unistd.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#define KRB5_PRIVATE 1 +#include <krb5.h> +#ifdef WITH_MOZLDAP +#include <mozldap/ldap.h> +#else +#include <ldap.h> +#endif +#include <sasl/sasl.h> +#include <popt.h> + +/* Salt types */ +#define NO_SALT -1 +#define KRB5_KDB_SALTTYPE_NORMAL 0 +#define KRB5_KDB_SALTTYPE_V4 1 +#define KRB5_KDB_SALTTYPE_NOREALM 2 +#define KRB5_KDB_SALTTYPE_ONLYREALM 3 +#define KRB5_KDB_SALTTYPE_SPECIAL 4 +#define KRB5_KDB_SALTTYPE_AFS3 5 + +#define KEYTAB_SET_OID "2.16.840.1.113730.3.8.3.1" +#define KEYTAB_RET_OID "2.16.840.1.113730.3.8.3.2" + +struct krb_key_salt { + krb5_enctype enctype; + krb5_int32 salttype; + krb5_keyblock key; + krb5_data salt; +}; + +struct keys_container { + krb5_int32 nkeys; + struct krb_key_salt *ksdata; +}; + +static int ldap_sasl_interact(LDAP *ld, unsigned flags, void *priv_data, void *sit) +{ + sasl_interact_t *in = NULL; + int ret = LDAP_OTHER; + krb5_principal princ = (krb5_principal)priv_data; + + if (!ld) return LDAP_PARAM_ERROR; + + for (in = sit; in && in->id != SASL_CB_LIST_END; in++) { + switch(in->id) { + case SASL_CB_USER: + in->result = princ->data[0].data; + in->len = princ->data[0].length; + ret = LDAP_SUCCESS; + break; + case SASL_CB_GETREALM: + in->result = princ->realm.data; + in->len = princ->realm.length; + ret = LDAP_SUCCESS; + break; + default: + in->result = NULL; + in->len = 0; + ret = LDAP_OTHER; + } + } + return ret; +} + +static void free_keys_contents(krb5_context krbctx, struct keys_container *keys) +{ + struct krb_key_salt *ksdata; + int i; + + ksdata = keys->ksdata; + for (i = 0; i < keys->nkeys; i++) { + krb5_free_keyblock_contents(krbctx, &ksdata[i].key); + krb5_free_data_contents(krbctx, &ksdata[i].salt); + } + free(ksdata); + + keys->ksdata = NULL; + keys->nkeys = 0; +} + +/* Determines Encryption and Salt types, + * allocates key_salt data storage, + * filters out equivalent encodings, + * returns 0 if no enctypes available, >0 if enctypes are available */ +static int prep_ksdata(krb5_context krbctx, const char *str, + struct keys_container *keys) +{ + struct krb_key_salt *ksdata; + krb5_error_code krberr; + int n, i, j, nkeys; + + if (str == NULL) { + krb5_enctype *ktypes; + + krberr = krb5_get_permitted_enctypes(krbctx, &ktypes); + if (krberr) { + fprintf(stderr, "No system preferred enctypes ?!\n"); + return 0; + } + + for (n = 0; ktypes[n]; n++) /* count */ ; + + ksdata = calloc(n + 1, sizeof(struct krb_key_salt)); + if (NULL == ksdata) { + fprintf(stderr, "Out of memory!?\n"); + return 0; + } + + for (i = 0; i < n; i++) { + ksdata[i].enctype = ktypes[i]; + ksdata[i].salttype = KRB5_KDB_SALTTYPE_NORMAL; + } + + krb5_free_ktypes(krbctx, ktypes); + + nkeys = i; + + } else { + char *tmp, *t, *p, *q; + + t = tmp = strdup(str); + if (!tmp) { + fprintf(stderr, "Out of memory\n"); + return 0; + } + + /* count */ + n = 0; + while ((p = strchr(t, ','))) { + t = p+1; + n++; + } + n++; /* count the last one that is 0 terminated instead */ + + /* at the end we will have at most n entries + 1 terminating */ + ksdata = calloc(n + 1, sizeof(struct krb_key_salt)); + if (!ksdata) { + fprintf(stderr, "Out of memory\n"); + return 0; + } + + for (i = 0, j = 0, t = tmp; i < n; i++) { + + p = strchr(t, ','); + if (p) *p = '\0'; + + q = strchr(t, ':'); + if (q) *q++ = '\0'; + + krberr = krb5_string_to_enctype(t, &ksdata[j].enctype); + if (krberr != 0) { + fprintf(stderr, + "Warning unrecognized encryption type: [%s]\n", t); + t = p+1; + continue; + } + t = p+1; + + if (!q) { + ksdata[j].salttype = KRB5_KDB_SALTTYPE_NORMAL; + j++; + continue; + } + + krberr = krb5_string_to_salttype(q, &ksdata[j].salttype); + if (krberr != 0) { + fprintf(stderr, "Warning unrecognized salt type: [%s]\n", q); + continue; + } + + j++; + } + + nkeys = j; + + free(tmp); + } + + /* Check we don't already have a key with a similar encoding, + * it would just produce redundant data and this is what the + * MIT code do anyway */ + + for (i = 0, n = 0; i < nkeys; i++ ) { + int similar = 0; + + for (j = 0; j < i; j++) { + krberr = krb5_c_enctype_compare(krbctx, + ksdata[j].enctype, + ksdata[i].enctype, + &similar); + if (krberr) { + free_keys_contents(krbctx, keys); + fprintf(stderr, "Enctype comparison failed!\n"); + return 0; + } + if (similar && + (ksdata[j].salttype == ksdata[i].salttype)) { + break; + } + } + if (j < i) { + /* redundant encoding, remove it, and shift others */ + int x; + for (x = i; x < nkeys-1; x++) { + ksdata[x].enctype = ksdata[x+1].enctype; + ksdata[x].salttype = ksdata[x+1].salttype; + } + continue; + } + /* count only confirmed enc/salt tuples */ + n++; + } + + keys->nkeys = n; + keys->ksdata = ksdata; + + return n; +} + +static int create_keys(krb5_context krbctx, + krb5_principal princ, + char *password, + const char *enctypes_string, + struct keys_container *keys) +{ + struct krb_key_salt *ksdata; + krb5_error_code krberr; + krb5_data key_password; + krb5_data *realm; + int i, j, nkeys; + int ret; + + ret = prep_ksdata(krbctx, enctypes_string, keys); + if (ret == 0) return 0; + + ksdata = keys->ksdata; + nkeys = keys->nkeys; + + if (password) { + key_password.data = password; + key_password.length = strlen(password); + + realm = krb5_princ_realm(krbctx, princ); + } + + for (i = 0; i < nkeys; i++) { + krb5_data *salt; + + if (!password) { + /* cool, random keys */ + krberr = krb5_c_make_random_key(krbctx, + ksdata[i].enctype, + &ksdata[i].key); + if (krberr) { + fprintf(stderr, "Failed to create random key!\n"); + return 0; + } + /* set the salt to NO_SALT as the key was random */ + ksdata[i].salttype = NO_SALT; + continue; + } + + /* Make keys using password and required salt */ + switch (ksdata[i].salttype) { + case KRB5_KDB_SALTTYPE_ONLYREALM: + krberr = krb5_copy_data(krbctx, realm, &salt); + if (krberr) { + fprintf(stderr, "Failed to create key!\n"); + return 0; + } + + ksdata[i].salt.length = salt->length; + ksdata[i].salt.data = malloc(salt->length); + if (!ksdata[i].salt.data) { + fprintf(stderr, "Out of memory!\n"); + return 0; + } + memcpy(ksdata[i].salt.data, salt->data, salt->length); + krb5_free_data(krbctx, salt); + break; + + case KRB5_KDB_SALTTYPE_NOREALM: + krberr = krb5_principal2salt_norealm(krbctx, princ, &ksdata[i].salt); + if (krberr) { + fprintf(stderr, "Failed to create key!\n"); + return 0; + } + break; + + case KRB5_KDB_SALTTYPE_NORMAL: + krberr = krb5_principal2salt(krbctx, princ, &ksdata[i].salt); + if (krberr) { + fprintf(stderr, "Failed to create key!\n"); + return 0; + } + break; + + /* no KRB5_KDB_SALTTYPE_V4, we do not support krb v4 */ + + case KRB5_KDB_SALTTYPE_AFS3: + /* Comment from MIT sources: + * * Why do we do this? Well, the afs_mit_string_to_key + * * needs to use strlen, and the realm is not NULL + * * terminated.... + */ + ksdata[i].salt.data = (char *)malloc(realm->length + 1); + if (NULL == ksdata[i].salt.data) { + fprintf(stderr, "Out of memory!\n"); + return 0; + } + memcpy((char *)ksdata[i].salt.data, + (char *)realm->data, realm->length); + ksdata[i].salt.data[realm->length] = '\0'; + /* AFS uses a special length (UGLY) */ + ksdata[i].salt.length = SALT_TYPE_AFS_LENGTH; + break; + + default: + fprintf(stderr, "Bad or unsupported salt type (%d)!\n", + ksdata[i].salttype); + return 0; + } + + krberr = krb5_c_string_to_key(krbctx, + ksdata[i].enctype, + &key_password, + &ksdata[i].salt, + &ksdata[i].key); + if (krberr) { + fprintf(stderr, "Failed to create key!\n"); + return 0; + } + + /* set back salt length to real value if AFS3 */ + if (ksdata[i].salttype == KRB5_KDB_SALTTYPE_AFS3) { + ksdata[i].salt.length = realm->length; + } + } + + return nkeys; +} + +static struct berval *create_key_control(struct keys_container *keys, + const char *principalName) +{ + struct krb_key_salt *ksdata; + struct berval *bval; + BerElement *be; + int ret, i; + + be = ber_alloc_t(LBER_USE_DER); + if (!be) { + return NULL; + } + + ret = ber_printf(be, "{s{", principalName); + if (ret == -1) { + ber_free(be, 1); + return NULL; + } + + ksdata = keys->ksdata; + for (i = 0; i < keys->nkeys; i++) { + + /* we set only the EncryptionKey and salt, no s2kparams */ + + ret = ber_printf(be, "{t[{t[i]t[o]}]", + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0), + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0), + (ber_int_t)ksdata[i].enctype, + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1), + (char *)ksdata[i].key.contents, (ber_len_t)ksdata[i].key.length); + + if (ret == -1) { + ber_free(be, 1); + return NULL; + } + + if (ksdata[i].salttype == NO_SALT) { + ret = ber_printf(be, "}"); + continue; + } + + /* we have to pass a salt structure */ + ret = ber_printf(be, "t[{t[i]t[o]}]}", + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1), + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0), + (ber_int_t)ksdata[i].salttype, + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1), + (char *)ksdata[i].salt.data, (ber_len_t)ksdata[i].salt.length); + + if (ret == -1) { + ber_free(be, 1); + return NULL; + } + } + + ret = ber_printf(be, "}}"); + if (ret == -1) { + ber_free(be, 1); + return NULL; + } + + ret = ber_flatten(be, &bval); + if (ret == -1) { + ber_free(be, 1); + return NULL; + } + + ber_free(be, 1); + return bval; +} + +int filter_keys(krb5_context krbctx, struct keys_container *keys, + ber_int_t *enctypes) +{ + struct krb_key_salt *ksdata; + int i, j, n; + + n = keys->nkeys; + ksdata = keys->ksdata; + for (i = 0; i < n; i++) { + if (ksdata[i].enctype == enctypes[i]) continue; + if (enctypes[i] == 0) { + /* remove unsupported one */ + krb5_free_keyblock_contents(krbctx, &ksdata[i].key); + krb5_free_data_contents(krbctx, &ksdata[i].salt); + for (j = i; j < n-1; j++) { + keys[j] = keys[j + 1]; + } + n--; + /* new key has been moved to this position, make sure + * we do not skip it, by neutralizing next i increment */ + i--; + } + } + + if (n == 0) { + fprintf(stderr, "No keys accepted by KDC\n"); + return 0; + } + + keys->nkeys = n; + return n; +} + +static int ldap_set_keytab(krb5_context krbctx, + const char *servername, + const char *principal_name, + krb5_principal princ, + struct keys_container *keys) +{ + int version; + LDAP *ld = NULL; + BerElement *sctrl = NULL; + struct berval *control = NULL; + char *retoid = NULL; + struct berval *retdata = NULL; + struct timeval tv; + LDAPMessage *res = NULL; + LDAPControl **srvctrl = NULL; + LDAPControl *pprc = NULL; + char *err = NULL; + int msgid; + int ret, rc; + int kvno, i; + ber_tag_t rtag; + ber_int_t *encs = NULL; + + /* cant' return more than nkeys, sometimes less */ + encs = calloc(keys->nkeys + 1, sizeof(ber_int_t)); + if (!encs) { + fprintf(stderr, "Out of Memory!\n"); + return 0; + } + + /* build password change control */ + control = create_key_control(keys, principal_name); + if (!control) { + fprintf(stderr, "Failed to create control!\n"); + goto error_out; + } + + /* TODO: support referrals ? */ + ld = ldap_init(servername, 389); + if(ld == NULL) { + fprintf(stderr, "Unable to initialize ldap library!\n"); + goto error_out; + } + + version = LDAP_VERSION3; + ret = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version); + if (ret != LDAP_SUCCESS) { + fprintf(stderr, "Unable to set ldap options!\n"); + goto error_out; + } + + ret = ldap_sasl_interactive_bind_s(ld, + NULL, "GSSAPI", + NULL, NULL, + LDAP_SASL_QUIET, + ldap_sasl_interact, princ); + if (ret != LDAP_SUCCESS) { + fprintf(stderr, "SASL Bind failed!\n"); + goto error_out; + } + + /* find base dn */ + /* TODO: address the case where we have multiple naming contexts */ + tv.tv_sec = 10; + tv.tv_usec = 0; + + /* perform password change */ + ret = ldap_extended_operation(ld, + KEYTAB_SET_OID, + control, NULL, NULL, + &msgid); + if (ret != LDAP_SUCCESS) { + fprintf(stderr, "Operation failed! %s\n", ldap_err2string(ret)); + goto error_out; + } + + ber_bvfree(control); + control = NULL; + + tv.tv_sec = 10; + tv.tv_usec = 0; + + ret = ldap_result(ld, msgid, 1, &tv, &res); + if (ret == -1) { + fprintf(stderr, "Operation failed! %s\n", ldap_err2string(ret)); + goto error_out; + } + + ret = ldap_parse_extended_result(ld, res, &retoid, &retdata, 0); + if(ret != LDAP_SUCCESS) { + fprintf(stderr, "Operation failed! %s\n", ldap_err2string(ret)); + goto error_out; + } + + ret = ldap_parse_result(ld, res, &rc, NULL, &err, NULL, &srvctrl, 0); + if(ret != LDAP_SUCCESS || rc != LDAP_SUCCESS) { + fprintf(stderr, "Operation failed! %s\n", err?err:ldap_err2string(ret)); + goto error_out; + } + + if (!srvctrl) { + fprintf(stderr, "Missing reply control!\n"); + goto error_out; + } + + for (i = 0; srvctrl[i]; i++) { + if (0 == strcmp(srvctrl[i]->ldctl_oid, KEYTAB_RET_OID)) { + pprc = srvctrl[i]; + } + } + if (!pprc) { + fprintf(stderr, "Missing reply control!\n"); + goto error_out; + } + + sctrl = ber_init(&pprc->ldctl_value); + + if (!sctrl) { + fprintf(stderr, "ber_init() failed, Invalid control ?!\n"); + goto error_out; + } + + /* Format of response + * + * KeytabGetRequest ::= SEQUENCE { + * new_kvno Int32 + * SEQUENCE OF KeyTypes + * } + * + * * List of accepted enctypes * + * KeyTypes ::= SEQUENCE { + * enctype Int32 + * } + */ + + rtag = ber_scanf(sctrl, "{i{", &kvno); + if (rtag == LBER_ERROR) { + fprintf(stderr, "ber_scanf() failed, Invalid control ?!\n"); + goto error_out; + } + + for (i = 0; i < keys->nkeys; i++) { + ret = ber_scanf(sctrl, "{i}", &encs[i]); + if (ret == LBER_ERROR) break; + } + + ret = filter_keys(krbctx, keys, encs); + if (ret == 0) goto error_out; + + if (err) ldap_memfree(err); + ber_free(sctrl, 1); + ldap_controls_free(srvctrl); + ldap_msgfree(res); + ldap_unbind_ext(ld, NULL, NULL); + return kvno; + +error_out: + if (sctrl) ber_free(sctrl, 1); + if (srvctrl) ldap_controls_free(srvctrl); + if (err) ldap_memfree(err); + if (res) ldap_msgfree(res); + if (ld) ldap_unbind_ext(ld, NULL, NULL); + if (control) ber_bvfree(control); + free(encs); + return 0; +} + +static char *ask_password(krb5_context krbctx) +{ + krb5_prompt ap_prompts[2]; + krb5_data k5d_pw0; + krb5_data k5d_pw1; + char pw0[256]; + char pw1[256]; + char *password; + + k5d_pw0.length = sizeof(pw0); + k5d_pw0.data = pw0; + ap_prompts[0].prompt = "New Principal Password"; + ap_prompts[0].hidden = 1; + ap_prompts[0].reply = &k5d_pw0; + + k5d_pw1.length = sizeof(pw1); + k5d_pw1.data = pw1; + ap_prompts[1].prompt = "Verify Principal Password"; + ap_prompts[1].hidden = 1; + ap_prompts[1].reply = &k5d_pw1; + + krb5_prompter_posix(krbctx, NULL, + NULL, NULL, + 2, ap_prompts); + + if (strcmp(pw0, pw1)) { + fprintf(stderr, "Passwords do not match!"); + return NULL; + } + + password = malloc(k5d_pw0.length + 1); + if (!password) return NULL; + memcpy(password, pw0, k5d_pw0.length); + password[k5d_pw0.length] = '\0'; + + return password; +} + +int main(int argc, char *argv[]) +{ + static const char *server = NULL; + static const char *principal = NULL; + static const char *keytab = NULL; + static const char *enctypes_string = NULL; + int quiet = 0; + int askpass = 0; + int permitted_enctypes = 0; + struct poptOption options[] = { + { "quiet", 'q', POPT_ARG_NONE, &quiet, 0, "Print as little as possible", "Output only on errors"}, + { "server", 's', POPT_ARG_STRING, &server, 0, "Contact this specific KDC Server", "Server Name" }, + { "principal", 'p', POPT_ARG_STRING, &principal, 0, "The principal to get a keytab for (ex: ftp/ftp.example.com@EXAMPLE.COM)", "Kerberos Service Principal Name" }, + { "keytab", 'k', POPT_ARG_STRING, &keytab, 0, "File were to store the keytab information", "Keytab File Name" }, + { "enctypes", 'e', POPT_ARG_STRING, &enctypes_string, 0, "Encryption types to request", "Comma separated encryption types list" }, + { "permitted-enctypes", 0, POPT_ARG_NONE, &permitted_enctypes, 0, "Show the list of permitted encryption types and exit", "Permitted Encryption Types"}, + { "password", 'P', POPT_ARG_NONE, &askpass, 0, "Asks for a non-random password to use for the principal" }, + { NULL, 0, POPT_ARG_NONE, NULL, 0, NULL, NULL } + }; + poptContext pc; + char *ktname; + char *password = NULL; + krb5_context krbctx; + krb5_ccache ccache; + krb5_principal uprinc; + krb5_principal sprinc; + krb5_error_code krberr; + ber_int_t *enctypes; + struct keys_container keys; + krb5_keytab kt; + int kvno; + int i, ret; + + krberr = krb5_init_context(&krbctx); + if (krberr) { + fprintf(stderr, "Kerberos context initialization failed\n"); + exit(1); + } + + pc = poptGetContext("ipa-getkeytab", argc, (const char **)argv, options, 0); + ret = poptGetNextOpt(pc); + if (ret == -1 && permitted_enctypes && + !(server || principal || keytab || quiet)) { + krb5_enctype *ktypes; + char enc[79]; /* fit std terminal or truncate */ + + krberr = krb5_get_permitted_enctypes(krbctx, &ktypes); + if (krberr) { + fprintf(stderr, "No system preferred enctypes ?!\n"); + exit(1); + } + fprintf(stdout, "Supported encryption types:\n"); + for (i = 0; ktypes[i]; i++) { + krberr = krb5_enctype_to_string(ktypes[i], enc, 79); + if (krberr) { + fprintf(stderr, "Warning: failed to convert type (#%d)\n", i); + continue; + } + fprintf(stdout, "%s\n", enc); + } + krb5_free_ktypes(krbctx, ktypes); + exit (0); + } + + if (ret != -1 || !server || !principal || !keytab || permitted_enctypes) { + if (!quiet) { + poptPrintUsage(pc, stderr, 0); + } + exit(2); + } + + if (askpass) { + password = ask_password(krbctx); + if (!password) { + exit(2); + } + } else if (enctypes_string && strchr(enctypes_string, ':')) { + if (!quiet) { + fprintf(stderr, "Warning: salt types are not honored with randomized passwords (see opt. -P)\n"); + } + } + + ret = asprintf(&ktname, "WRFILE:%s", keytab); + if (ret == -1) { + exit(3); + } + + krberr = krb5_parse_name(krbctx, principal, &sprinc); + if (krberr) { + fprintf(stderr, "Invalid Service Principal Name\n"); + exit(4); + } + + krberr = krb5_cc_default(krbctx, &ccache); + if (krberr) { + fprintf(stderr, "Kerberos Credential Cache not found\n" + "Do you have a Kerberos Ticket?\n"); + exit(5); + } + + krberr = krb5_cc_get_principal(krbctx, ccache, &uprinc); + if (krberr) { + fprintf(stderr, "Kerberos User Principal not found\n" + "Do you have a valid Credential Cache?\n"); + exit(6); + } + + krberr = krb5_kt_resolve(krbctx, ktname, &kt); + if (krberr) { + fprintf(stderr, "Failed to open Keytab\n"); + exit(7); + } + + /* create key material */ + ret = create_keys(krbctx, sprinc, password, enctypes_string, &keys); + if (!ret) { + fprintf(stderr, "Failed to create key material\n"); + exit(8); + } + + kvno = ldap_set_keytab(krbctx, server, principal, uprinc, &keys); + if (!kvno) { + exit(9); + } + + for (i = 0; i < keys.nkeys; i++) { + krb5_keytab_entry kt_entry; + memset((char *)&kt_entry, 0, sizeof(kt_entry)); + kt_entry.principal = sprinc; + kt_entry.key = keys.ksdata[i].key; + kt_entry.vno = kvno; + + krberr = krb5_kt_add_entry(krbctx, kt, &kt_entry); + if (krberr) { + fprintf(stderr, "Failed to add key to the keytab\n"); + exit (11); + } + } + + free_keys_contents(krbctx, &keys); + + krberr = krb5_kt_close(krbctx, kt); + if (krberr) { + fprintf(stderr, "Failed to close the keytab\n"); + exit (12); + } + + if (!quiet) { + fprintf(stderr, + "Keytab successfully retrieved and stored in: %s\n", + keytab); + } + exit(0); +} diff --git a/ipa-client/ipa-install/Makefile.am b/ipa-client/ipa-install/Makefile.am new file mode 100644 index 00000000..ad0c4e0c --- /dev/null +++ b/ipa-client/ipa-install/Makefile.am @@ -0,0 +1,13 @@ +NULL = + +sbin_SCRIPTS = \ + ipa-client-install \ + $(NULL) + +EXTRA_DIST = \ + $(sbin_SCRIPTS) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install new file mode 100644 index 00000000..cd5bfdde --- /dev/null +++ b/ipa-client/ipa-install/ipa-client-install @@ -0,0 +1,387 @@ +#! /usr/bin/python -E +# Authors: Simo Sorce <ssorce@redhat.com> +# Karl MacMillan <kmacmillan@mentalrootkit.com> +# +# Copyright (C) 2007 Red Hat +# see file 'COPYING' for use and warranty information +# +# 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; version 2 only +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +try: + import sys + + import os + import krbV + import socket + import logging + from optparse import OptionParser + import ipaclient.ipadiscovery + import ipaclient.ipachangeconf + import ipaclient.ntpconf + from ipa.ipautil import run, user_input + from ipa import sysrestore + from ipa import version +except ImportError: + print >> sys.stderr, """\ +There was a problem importing one of the required Python modules. The +error was: + + %s +""" % sys.exc_value + sys.exit(1) + + +def parse_options(): + parser = OptionParser(version=version.VERSION) + parser.add_option("--domain", dest="domain", help="domain name") + parser.add_option("--server", dest="server", help="IPA server") + parser.add_option("--realm", dest="realm_name", help="realm name") + parser.add_option("-f", "--force", dest="force", action="store_true", + default=False, help="force setting of ldap/kerberos conf") + parser.add_option("-d", "--debug", dest="debug", action="store_true", + default=False, help="print debugging information") + parser.add_option("-U", "--unattended", dest="unattended", + action="store_true", + help="unattended installation never prompts the user") + parser.add_option("--ntp-server", dest="ntp_server", help="ntp server to use") + parser.add_option("-N", "--no-ntp", action="store_false", + help="do not configure ntp", default=True, dest="conf_ntp") + parser.add_option("--on-master", dest="on_master", action="store_true", + help="use this option when run on a master", default=False) + parser.add_option("", "--uninstall", dest="uninstall", action="store_true", + default=False, help="uninstall an existing installation") + + options, args = parser.parse_args() + + if (options.server and not options.domain): + parser.error("--server cannot be used without providing --domain") + + return options + +def logging_setup(options): + # Always log everything (i.e., DEBUG) to the log + # file. + + log_file = "/var/log/ipaclient-install.log" + if options.uninstall: + log_file = "/var/log/ipaclient-uninstall.log" + + old_umask = os.umask(077) + logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s %(levelname)s %(message)s', + filename=log_file, + filemode='w') + os.umask(old_umask) + + console = logging.StreamHandler() + # If the debug option is set, also log debug messages to the console + if options.debug: + console.setLevel(logging.DEBUG) + else: + # Otherwise, log critical and error messages + console.setLevel(logging.ERROR) + formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') + console.setFormatter(formatter) + logging.getLogger('').addHandler(console) + +def uninstall(options): + + print "Restoring client configuration files" + fstore.restore_all_files() + + print "Disabling client Kerberos and Ldap configurations" + try: + run(["/usr/sbin/authconfig", "--disableldap", "--disablekrb5", "--update"]) + except Exception, e: + print "Failed to remove krb5/ldap configuration. " +str(e) + sys.exit(1) + + try: + run(["/sbin/service", "nscd", "restart"]) + except: + print "Failed to restart start the NSCD daemon" + + if not options.unattended: + print "The original nsswitch.conf configuration has been restored." + print "You may need to restart services or reboot the machine." + if not options.on_master: + if user_input("Do you want to reboot the machine?", False): + try: + run(["/usr/bin/reboot"]) + except Exception, e: + print "Reboot command failed to exceute. " + str(e) + sys.exit(1) + +def main(): + options = parse_options() + logging_setup(options) + dnsok = True + + global fstore + fstore = sysrestore.FileStore('/var/lib/ipa-client/sysrestore') + + if options.uninstall: + return uninstall(options) + + cli_domain = None + cli_server = None + cli_realm = None + cli_basedn = None + + # Create the discovery instance + ds = ipaclient.ipadiscovery.IPADiscovery() + + ret = ds.search(domain=options.domain, server=options.server) + if ret == -10: + print "Can't get the fully qualified name of this host" + print "Please check that the client is properly configured" + return ret + if ret == -1 or not ds.getDomainName(): + logging.debug("Domain not found") + if options.domain: + cli_domain = options.domain + elif options.unattended: + return ret + else: + print "DNS discovery failed to determine your DNS domain" + cli_domain = user_input("Please provide the domain name of your IPA server (ex: example.com)", allow_empty = False) + ret = ds.search(domain=cli_domain, server=options.server) + if not cli_domain: + if ds.getDomainName(): + cli_domain = ds.getDomainName() + + if ret == -2 or not ds.getServerName(): + dnsok = False + logging.debug("IPA Server not found") + if options.server: + cli_server = options.server + elif options.unattended: + return ret + else: + print "DNS discovery failed to find the IPA Server" + cli_server = user_input("Please provide your IPA server name (ex: ipa.example.com)", allow_empty = False) + ret = ds.search(domain=cli_domain, server=cli_server) + if not cli_server: + if ds.getServerName(): + cli_server = ds.getServerName() + + if ret != 0: + print "Failed to verify that "+cli_server+" is an IPA Server." + print "This may mean that the remote server is not up or is not reachable" + print "due to network or firewall settings." + return ret + + if dnsok: + print "Discovery was successful!" + elif not options.unattended: + print "\nThe failure to use DNS to find your IPA server indicates that your" + print "resolv.conf file is not properly configured.\n" + print "Autodiscovery of servers for failover cannot work with this configuration.\n" + print "If you proceed with the installation, services will be configured to always" + print "access the discovered server for all operation and will not fail over to" + print "other servers in case of failure.\n" + if not user_input("Do you want to proceed and configure the system with fixed values with no DNS discovery?", False): + return ret + + if options.realm_name and options.realm_name != ds.getRealmName(): + if not options.unattended: + print "ERROR: The provided realm name: ["+options.realm_name+"] does not match with the discovered one: ["+ds.getRealmName()+"]\n" + return -3 + + cli_realm = ds.getRealmName() + cli_basedn = ds.getBaseDN() + + print "Realm: "+cli_realm + print "DNS Domain: "+cli_domain + print "IPA Server: "+cli_server + print "BaseDN: "+cli_basedn + + print "\n" + if not options.unattended and not user_input("Continue to configure the system with these values?", False): + return 1 + + # Configure ipa.conf + if not options.on_master: + ipaconf = ipaclient.ipachangeconf.IPAChangeConf("IPA Installer") + ipaconf.setOptionAssignment(" = ") + ipaconf.setSectionNameDelimiters(("[","]")) + + opts = [{'name':'comment', 'type':'comment', 'value':'File modified by ipa-client-install'}, + {'name':'empty', 'type':'empty'}] + + #[defaults] + defopts = [{'name':'server', 'type':'option', 'value':cli_server}, + {'name':'realm', 'type':'option', 'value':cli_realm}, + {'name':'domain', 'type':'option', 'value':cli_domain}] + + opts.append({'name':'defaults', 'type':'section', 'value':defopts}) + opts.append({'name':'empty', 'type':'empty'}) + + fstore.backup_file("/etc/ipa/ipa.conf") + ipaconf.newConf("/etc/ipa/ipa.conf", opts) + print "Created /etc/ipa/ipa.conf" + + + # Configure ldap.conf + ldapconf = ipaclient.ipachangeconf.IPAChangeConf("IPA Installer") + ldapconf.setOptionAssignment(" ") + + opts = [{'name':'comment', 'type':'comment', 'value':'File modified by ipa-client-install'}, + {'name':'empty', 'type':'empty'}, + {'name':'ldap_version', 'type':'option', 'value':'3'}, + {'name':'base', 'type':'option', 'value':cli_basedn}, + {'name':'empty', 'type':'empty'}, + {'name':'nss_base_passwd', 'type':'option', 'value':'cn=users,cn=accounts,'+cli_basedn+'?sub'}, + {'name':'nss_base_group', 'type':'option', 'value':'cn=groups,cn=accounts,'+cli_basedn+'?sub'}, + {'name':'nss_schema', 'type':'option', 'value':'rfc2307bis'}, + {'name':'nss_map_attribute', 'type':'option', 'value':'uniqueMember member'}, + {'name':'nss_initgroups_ignoreusers', 'type':'option', 'value':'root,dirsrv'}, + {'name':'empty', 'type':'empty'}, + {'name':'nss_reconnect_maxsleeptime', 'type':'option', 'value':'8'}, + {'name':'nss_reconnect_sleeptime', 'type':'option', 'value':'1'}, + {'name':'bind_timelimit', 'type':'option', 'value':'5'}, + {'name':'timelimit', 'type':'option', 'value':'15'}, + {'name':'empty', 'type':'empty'}] + if not dnsok or options.force or options.on_master: + if options.on_master: + opts.append({'name':'uri', 'type':'option', 'value':'ldap://localhost'}) + else: + opts.append({'name':'uri', 'type':'option', 'value':'ldap://'+cli_server}) + else: + opts.append({'name':'nss_srv_domain', 'type':'option', 'value':cli_domain}) + + opts.append({'name':'empty', 'type':'empty'}) + try: + fstore.backup_file("/etc/ldap.conf") + ldapconf.newConf("/etc/ldap.conf", opts) + print "Configured /etc/ldap.conf" + except Exception, e: + print "Creation of /etc/ldap.conf: " + str(e) + return 1 + + #If on master assume kerberos is already configured properly. + if not options.on_master: + + #Configure krb5.conf + krbconf = ipaclient.ipachangeconf.IPAChangeConf("IPA Installer") + krbconf.setOptionAssignment(" = ") + krbconf.setSectionNameDelimiters(("[","]")) + krbconf.setSubSectionDelimiters(("{","}")) + krbconf.setIndent((""," "," ")) + + opts = [{'name':'comment', 'type':'comment', 'value':'File modified by ipa-client-install'}, + {'name':'empty', 'type':'empty'}] + + #[libdefaults] + libopts = [{'name':'default_realm', 'type':'option', 'value':cli_realm}] + if not dnsok or options.force: + libopts.append({'name':'dns_lookup_realm', 'type':'option', 'value':'false'}) + libopts.append({'name':'dns_lookup_kdc', 'type':'option', 'value':'false'}) + else: + libopts.append({'name':'dns_lookup_realm', 'type':'option', 'value':'true'}) + libopts.append({'name':'dns_lookup_kdc', 'type':'option', 'value':'true'}) + libopts.append({'name':'ticket_lifetime', 'type':'option', 'value':'24h'}) + libopts.append({'name':'forwardable', 'type':'option', 'value':'yes'}) + + opts.append({'name':'libdefaults', 'type':'section', 'value':libopts}) + opts.append({'name':'empty', 'type':'empty'}) + + #the following are necessary only if DNS discovery does not work + if not dnsok or options.force: + #[realms] + kropts =[{'name':'kdc', 'type':'option', 'value':cli_server+':88'}, + {'name':'admin_server', 'type':'option', 'value':cli_server+':749'}, + {'name':'default_domain', 'type':'option', 'value':cli_domain}] + ropts = [{'name':cli_realm, 'type':'subsection', 'value':kropts}] + opts.append({'name':'realms', 'type':'section', 'value':ropts}) + opts.append({'name':'empty', 'type':'empty'}) + + #[domain_realm] + dropts = [{'name':'.'+cli_domain, 'type':'option', 'value':cli_realm}, + {'name':cli_domain, 'type':'option', 'value':cli_realm}] + opts.append({'name':'domain_realm', 'type':'section', 'value':dropts}) + opts.append({'name':'empty', 'type':'empty'}) + + #[appdefaults] + pamopts = [{'name':'debug', 'type':'option', 'value':'false'}, + {'name':'ticket_lifetime', 'type':'option', 'value':'36000'}, + {'name':'renew_lifetime', 'type':'option', 'value':'36000'}, + {'name':'forwardable', 'type':'option', 'value':'true'}, + {'name':'krb4_convert', 'type':'option', 'value':'false'}] + appopts = [{'name':'pam', 'type':'subsection', 'value':pamopts}] + opts.append({'name':'appdefaults', 'type':'section', 'value':appopts}) + + fstore.backup_file("/etc/krb5.conf") + krbconf.newConf("/etc/krb5.conf", opts); + print "Configured /etc/krb5.conf for IPA realm " + cli_realm + + #Modify nsswitch to add nss_ldap + run(["/usr/sbin/authconfig", "--enableldap", "--update"]) + print "LDAP enabled" + + #Check nss_ldap is working properly + if not options.on_master: + try: + run(["getent", "passwd", "admin"]) + except Exception, e: + print "nss_ldap is not able to use DNS discovery!" + print "Changing configuration to use hardcoded server name: " +cli_server + + opts = [{'name':'uri', 'type':'option', 'action':'set', 'value':'ldap://'+cli_server}, + {'name':'empty', 'type':'empty'}] + try: + ldapconf.changeConf("/etc/ldap.conf", opts) + except Exception, e: + print "Adding hardcoded server name to /etc/ldap.conf failed: " + str(e) + return 1 + + #Modify pam to add pam_krb5 + run(["/usr/sbin/authconfig", "--enablekrb5", "--update"]) + print "Kerberos 5 enabled" + + if options.conf_ntp and not options.on_master: + if options.ntp_server: + ntp_server = options.ntp_server + else: + ntp_server = cli_server + ipaclient.ntpconf.config_ntp(ntp_server, fstore) + print "NTP enabled" + + #Activate Name Server Caching Daemon + try: + run(["/sbin/service", "nscd", "restart"]) + except: + print "Failed to start the NSCD daemon" + print "Caching of users/groups will not be available" + pass + + try: + run(["/sbin/chkconfig", "nscd", "on"]) + except: + print "Failed to configure automatic startup of the NSCD daemon" + print "Caching of users/groups will not be available after reboot" + pass + + print "Client configuration complete." + + return 0 + +try: + if __name__ == "__main__": + sys.exit(main()) +except SystemExit, e: + sys.exit(e) +except KeyboardInterrupt: + sys.exit(1) diff --git a/ipa-client/ipaclient/Makefile.am b/ipa-client/ipaclient/Makefile.am new file mode 100644 index 00000000..1d7df526 --- /dev/null +++ b/ipa-client/ipaclient/Makefile.am @@ -0,0 +1,16 @@ +NULL = + +appdir = $(pythondir)/ipaclient +app_PYTHON = \ + __init__.py \ + ipachangeconf.py \ + ipadiscovery.py \ + ntpconf.py \ + $(NULL) + +EXTRA_DIST = \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in diff --git a/ipa-client/ipaclient/__init__.py b/ipa-client/ipaclient/__init__.py new file mode 100644 index 00000000..3eabc0f3 --- /dev/null +++ b/ipa-client/ipaclient/__init__.py @@ -0,0 +1,21 @@ +# Authors: Simo Sorce <ssorce@redhat.com> +# +# Copyright (C) 2007 Red Hat +# see file 'COPYING' for use and warranty information +# +# 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; version 2 or later +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +__all__ = ["ipadiscovery", "ipachangeconf"] + diff --git a/ipa-client/ipaclient/ipachangeconf.py b/ipa-client/ipaclient/ipachangeconf.py new file mode 100644 index 00000000..34c08d10 --- /dev/null +++ b/ipa-client/ipaclient/ipachangeconf.py @@ -0,0 +1,459 @@ +# +# ipachangeconf - configuration file manipulation classes and functions +# partially based on authconfig code +# Copyright (c) 1999-2007 Red Hat, Inc. +# Author: Simo Sorce <ssorce@redhat.com> +# +# This 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; version 2 only +# +# 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +import fcntl +import os +import string +import time +import shutil + +def openLocked(filename, perms): + fd = -1 + try: + fd = os.open(filename, os.O_RDWR | os.O_CREAT, perms) + + fcntl.lockf(fd, fcntl.LOCK_EX) + except OSError, (errno, strerr): + if fd != -1: + try: + os.close(fd) + except OSError: + pass + raise IOError(errno, strerr) + return os.fdopen(fd, "r+") + + + #TODO: add subsection as a concept + # (ex. REALM.NAME = { foo = x bar = y } ) + #TODO: put section delimiters as separating element of the list + # so that we can process multiple sections in one go + #TODO: add a comment all but provided options as a section option +class IPAChangeConf: + + def __init__(self, name): + self.progname = name + self.indent = ("","","") + self.assign = (" = ","=") + self.dassign = self.assign[0] + self.comment = ("#",) + self.dcomment = self.comment[0] + self.eol = ("\n",) + self.deol = self.eol[0] + self.sectnamdel = ("[","]") + self.subsectdel = ("{","}") + + def setProgName(self, name): + self.progname = name + + def setIndent(self, indent): + if type(indent) is tuple: + self.indent = indent + elif type(indent) is str: + self.indent = (indent, ) + else: + raise ValueError, 'Indent must be a list of strings' + + def setOptionAssignment(self, assign): + if type(assign) is tuple: + self.assign = assign + else: + self.assign = (assign, ) + self.dassign = self.assign[0] + + def setCommentPrefix(self, comment): + if type(comment) is tuple: + self.comment = comment + else: + self.comment = (comment, ) + self.dcomment = self.comment[0] + + def setEndLine(self, eol): + if type(eol) is tuple: + self.eol = eol + else: + self.eol = (eol, ) + self.deol = self.eol[0] + + def setSectionNameDelimiters(self, delims): + self.sectnamdel = delims + + def setSubSectionDelimiters(self, delims): + self.subsectdel = delims + + def matchComment(self, line): + for v in self.comment: + if line.lstrip().startswith(v): + return line.lstrip()[len(v):] + return False + + def matchEmpty(self, line): + if line.strip() == "": + return True + return False + + def matchSection(self, line): + cl = "".join(line.strip().split()).lower() + if len(self.sectnamdel) != 2: + return False + if not cl.startswith(self.sectnamdel[0]): + return False + if not cl.endswith(self.sectnamdel[1]): + return False + return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])] + + def matchSubSection(self, line): + if self.matchComment(line): + return False + + parts = line.split(self.dassign, 1) + if len(parts) < 2: + return False + + if parts[1].strip() == self.subsectdel[0]: + return parts[0].strip() + + return False + + def matchSubSectionEnd(self, line): + if self.matchComment(line): + return False + + if line.strip() == self.subsectdel[1]: + return True + + return False + + def getSectionLine(self, section): + if len(self.sectnamdel) != 2: + return section + return self.sectnamdel[0]+section+self.sectnamdel[1]+self.deol + + def dump(self, options, level=0): + output = "" + if level >= len(self.indent): + level = len(self.indent)-1 + + for o in options: + if o['type'] == "section": + output += self.sectnamdel[0]+o['name']+self.sectnamdel[1]+self.deol + output += self.dump(o['value'], level+1) + continue + if o['type'] == "subsection": + output += self.indent[level]+o['name']+self.dassign+self.subsectdel[0]+self.deol + output += self.dump(o['value'], level+1) + output += self.indent[level]+self.subsectdel[1]+self.deol + continue + if o['type'] == "option": + output += self.indent[level]+o['name']+self.dassign+o['value']+self.deol + continue + if o['type'] == "comment": + output += self.dcomment+o['value']+self.deol + continue + if o['type'] == "empty": + output += self.deol + continue + raise SyntaxError, 'Unknown type: ['+o['type']+']' + + return output + + def parseLine(self, line): + + if self.matchEmpty(line): + return {'name':'empty', 'type':'empty'} + + value = self.matchComment(line) + if value: + return {'name':'comment', 'type':'comment', 'value':value.rstrip()} + + parts = line.split(self.dassign, 1) + if len(parts) < 2: + raise SyntaxError, 'Syntax Error: Unknown line format' + + return {'name':parts[0].strip(), 'type':'option', 'value':parts[1].rstrip()} + + def findOpts(self, opts, type, name, exclude_sections=False): + + num = 0 + for o in opts: + if o['type'] == type and o['name'] == name: + return (num, o) + if exclude_sections and (o['type'] == "section" or o['type'] == "subsection"): + return (num, None) + num += 1 + return (num, None) + + def commentOpts(self, inopts, level = 0): + + opts = [] + + if level >= len(self.indent): + level = len(self.indent)-1 + + for o in inopts: + if o['type'] == 'section': + no = self.commentOpts(o['value'], level+1) + val = self.dcomment+self.sectnamdel[0]+o['name']+self.sectnamdel[1] + opts.append({'name':'comment', 'type':'comment', 'value':val}) + for n in no: + opts.append(n) + continue + if o['type'] == 'subsection': + no = self.commentOpts(o['value'], level+1) + val = self.indent[level]+o['name']+self.dassign+self.subsectdel[0] + opts.append({'name':'comment', 'type':'comment', 'value':val}) + for n in no: + opts.append(n) + val = self.indent[level]+self.subsectdel[1] + opts.append({'name':'comment', 'type':'comment', 'value':val}) + continue + if o['type'] == 'option': + val = self.indent[level]+o['name']+self.dassign+o['value'] + opts.append({'name':'comment', 'type':'comment', 'value':val}) + continue + if o['type'] == 'comment': + opts.append(o) + continue + if o['type'] == 'empty': + opts.append({'name':'comment', 'type':'comment', 'value':''}) + continue + raise SyntaxError, 'Unknown type: ['+o['type']+']' + + return opts + + def mergeOld(self, oldopts, newopts): + + opts = [] + + for o in oldopts: + if o['type'] == "section" or o['type'] == "subsection": + (num, no) = self.findOpts(newopts, o['type'], o['name']) + if not no: + opts.append(o) + continue + if no['action'] == "set": + mo = self.mergeOld(o['value'], no['value']) + opts.append({'name':o['name'], 'type':o['type'], 'value':mo}) + continue + if no['action'] == "comment": + co = self.commentOpts(o['value']) + for c in co: + opts.append(c) + continue + if no['action'] == "remove": + continue + raise SyntaxError, 'Unknown action: ['+no['action']+']' + + if o['type'] == "comment" or o['type'] == "empty": + opts.append(o) + continue + + if o['type'] == "option": + (num, no) = self.findOpts(newopts, 'option', o['name'], True) + if not no: + opts.append(o) + continue + if no['action'] == 'comment' or no['action'] == 'remove': + if no['value'] != None and o['value'] != no['value']: + opts.append(o) + continue + if no['action'] == 'comment': + opts.append({'name':'comment', 'type':'comment', + 'value':self.dcomment+o['name']+self.dassign+o['value']}) + continue + if no['action'] == 'set': + opts.append(no) + continue + raise SyntaxError, 'Unknown action: ['+o['action']+']' + + raise SyntaxError, 'Unknown type: ['+o['type']+']' + + return opts + + def mergeNew(self, opts, newopts): + + cline = 0 + + for no in newopts: + + if no['type'] == "section" or no['type'] == "subsection": + (num, o) = self.findOpts(opts, no['type'], no['name']) + if not o: + if no['action'] == 'set': + opts.append(no) + continue + if no['action'] == "set": + self.mergeNew(o['value'], no['value']) + continue + cline = num+1 + continue + + if no['type'] == "option": + (num, o) = self.findOpts(opts, no['type'], no['name'], True) + if not o: + if no['action'] == 'set': + opts.append(no) + continue + cline = num+1 + continue + + if no['type'] == "comment" or no['type'] == "empty": + opts.insert(cline, no) + cline += 1 + continue + + raise SyntaxError, 'Unknown type: ['+no['type']+']' + + + def merge(self, oldopts, newopts): + + #Use a two pass strategy + #First we create a new opts tree from oldopts removing/commenting + # the options as indicated by the contents of newopts + #Second we fill in the new opts tree with options as indicated + # in the newopts tree (this is becaus eentire (sub)sections may + # exist in the newopts that do not exist in oldopts) + + opts = self.mergeOld(oldopts, newopts) + self.mergeNew(opts, newopts) + return opts + + #TODO: Make parse() recursive? + def parse(self, f): + + opts = [] + sectopts = [] + section = None + subsectopts = [] + subsection = None + curopts = opts + fatheropts = opts + + # Read in the old file. + for line in f: + + # It's a section start. + value = self.matchSection(line) + if value: + if section is not None: + opts.append({'name':section, 'type':'section', 'value':sectopts}) + sectopts = [] + curopts = sectopts + fatheropts = sectopts + section = value + continue + + # It's a subsection start. + value = self.matchSubSection(line) + if value: + if subsection is not None: + raise SyntaxError, 'nested subsections are not supported yet' + subsectopts = [] + curopts = subsectopts + subsection = value + continue + + value = self.matchSubSectionEnd(line) + if value: + if subsection is None: + raise SyntaxError, 'Unmatched end subsection terminator found' + fatheropts.append({'name':subsection, 'type':'subsection', 'value':subsectopts}) + subsection = None + curopts = fatheropts + continue + + # Copy anything else as is. + curopts.append(self.parseLine(line)) + + #Add last section if any + if len(sectopts) is not 0: + opts.append({'name':section, 'type':'section', 'value':sectopts}) + + return opts + + # Write settings to configuration file + # file is a path + # options is a set of dictionaries in the form: + # [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}] + # section is a section name like 'global' + def changeConf(self, file, newopts): + autosection = False + savedsection = None + done = False + output = "" + f = None + try: + #Do not catch an unexisting file error, we want to fail in that case + shutil.copy2(file, file+".ipabkp") + + f = openLocked(file, 0644) + + oldopts = self.parse(f) + + options = self.merge(oldopts, newopts) + + output = self.dump(options) + + # Write it out and close it. + f.seek(0) + f.truncate(0) + f.write(output) + finally: + try: + if f: + f.close() + except IOError: + pass + return True + + # Write settings to new file, backup old + # file is a path + # options is a set of dictionaries in the form: + # [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}] + # section is a section name like 'global' + def newConf(self, file, options): + autosection = False + savedsection = None + done = False + output = "" + f = None + try: + try: + shutil.copy2(file, file+".ipabkp") + except IOError, err: + if err.errno == 2: + # The orign file did not exist + pass + + f = openLocked(file, 0644) + + # Trunkate + f.seek(0) + f.truncate(0) + + output = self.dump(options) + + f.write(output) + finally: + try: + if f: + f.close() + except IOError: + pass + return True diff --git a/ipa-client/ipaclient/ipadiscovery.py b/ipa-client/ipaclient/ipadiscovery.py new file mode 100644 index 00000000..2bd15192 --- /dev/null +++ b/ipa-client/ipaclient/ipadiscovery.py @@ -0,0 +1,248 @@ +# Authors: Simo Sorce <ssorce@redhat.com> +# +# Copyright (C) 2007 Red Hat +# see file 'COPYING' for use and warranty information +# +# 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; version 2 only +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import socket +import logging +import ipa.dnsclient +import ldap +from ldap import LDAPError + +class IPADiscovery: + + def __init__(self): + self.realm = None + self.domain = None + self.server = None + self.basedn = None + + def getServerName(self): + return self.server + + def getDomainName(self): + return self.domain + + def getRealmName(self): + return self.realm + + def getBaseDN(self): + return self.basedn + + def search(self, domain = "", server = ""): + hostname = "" + qname = "" + results = [] + result = [] + krbret = [] + ldapret = [] + + if not server: + + if not domain: #domain not provided do full DNS discovery + + # get the local host name + hostname = socket.getfqdn() + if not hostname: + return -10 #bad host configuration + + # first, check for an LDAP server for the local domain + p = hostname.find(".") + if p == -1: #no domain name + return -1 + domain = hostname[p+1:] + + while not self.server: + logging.debug("[ipadnssearchldap("+domain+")]") + self.server = self.ipadnssearchldap(domain) + if self.server: + self.domain = domain + else: + p = domain.find(".") + if p == -1: #no ldap server found and last component of the domain already tested + return -1 + domain = domain[p+1:] + else: + logging.debug("[ipadnssearchldap]") + self.server = self.ipadnssearchldap(domain) + if self.server: + self.domain = domain + else: + return -2 #no ldap server found + + + #search for kerberos TODO: move this after ipacheckldap() + logging.debug("[ipadnssearchkrb]") + krbret = self.ipadnssearchkrb(self.domain) + if not krbret: + return -3 #no krb server found + + self.realm = krbret[0] + + else: #server forced on us, this means DNS doesn't work :/ + + self.domain = domain + self.server = server + + logging.debug("[ipacheckldap]") + # check ldap now + ldapret = self.ipacheckldap(self.server, self.realm) + + if not ldapret: + return -4 # not an IPA server (or broken config) + + self.server = ldapret[0] + self.realm = ldapret[1] + + return 0 + + def ipacheckldap(self, thost, trealm): + + lret = [] + lres = [] + lattr = "" + linfo = "" + lrealms = [] + + i = 0 + + #now verify the server is really an IPA server + try: + logging.debug("Init ldap with: ldap://"+thost+":389") + lh = ldap.initialize("ldap://"+thost+":389") + lh.simple_bind_s("","") + + logging.debug("Search rootdse") + lret = lh.search_s("", ldap.SCOPE_BASE, "(objectClass=*)") + for lattr in lret[0][1]: + if lattr.lower() == "namingcontexts": + self.basedn = lret[0][1][lattr][0] + + logging.debug("Search for (info=*) in "+self.basedn+"(base)") + lret = lh.search_s(self.basedn, ldap.SCOPE_BASE, "(info=IPA*)") + if not lret: + return [] + logging.debug("Found: "+str(lret)) + + for lattr in lret[0][1]: + if lattr.lower() == "info": + linfo = lret[0][1][lattr][0].lower() + break + + if not linfo: + return [] + + #search and return known realms + logging.debug("Search for (objectClass=krbRealmContainer) in "+self.basedn+"(sub)") + lret = lh.search_s("cn=kerberos,"+self.basedn, ldap.SCOPE_SUBTREE, "(objectClass=krbRealmContainer)") + if not lret: + #something very wrong + return [] + logging.debug("Found: "+str(lret)) + + for lres in lret: + for lattr in lres[1]: + if lattr.lower() == "cn": + lrealms.append(lres[1][lattr][0]) + + + if trealm: + for r in lrealms: + if trealm == r: + return [thost, trealm] + # must match or something is very wrong + return [] + else: + if len(lrealms) != 1: + #which one? we can't attach to a multi-realm server without DNS working + return [] + else: + return [thost, lrealms[0]] + + #we shouldn't get here + return [] + + except LDAPError, err: + #no good + try: + if type(err.message) == dict: + for (k, v) in err.message.iteritems(): + logging.error("LDAP Error: %s" % v ) + else: + logging.error("LDAP Error: "+err.message) + except AttributeError: + logging.error("LDAP Error: "+str(err)) + return [] + + + def ipadnssearchldap(self, tdomain): + servers = "" + rserver = "" + + qname = "_ldap._tcp."+tdomain + # terminate the name + if not qname.endswith("."): + qname += "." + results = ipa.dnsclient.query(qname, ipa.dnsclient.DNS_C_IN, ipa.dnsclient.DNS_T_SRV) + + for result in results: + if result.dns_type == ipa.dnsclient.DNS_T_SRV: + rserver = result.rdata.server.rstrip(".") + if result.rdata.port and result.rdata.port != 389: + rserver += ":" + str(result.rdata.port) + if servers: + servers += "," + rserver + else: + servers = rserver + break + + return servers + + def ipadnssearchkrb(self, tdomain): + realm = "" + kdc = "" + # now, check for a Kerberos realm the local host or domain is in + qname = "_kerberos." + tdomain + # terminate the name + if not qname.endswith("."): + qname += "." + results = ipa.dnsclient.query(qname, ipa.dnsclient.DNS_C_IN, ipa.dnsclient.DNS_T_TXT) + + for result in results: + if result.dns_type == ipa.dnsclient.DNS_T_TXT: + realm = result.rdata.data + if realm: + break + + if realm: + # now fetch server information for the realm + qname = "_kerberos._udp." + tdomain + # terminate the name + if not qname.endswith("."): + qname += "." + results = ipa.dnsclient.query(qname, ipa.dnsclient.DNS_C_IN, ipa.dnsclient.DNS_T_SRV) + for result in results: + if result.dns_type == ipa.dnsclient.DNS_T_SRV: + qname = result.rdata.server.rstrip(".") + if result.rdata.port and result.rdata.port != 88: + qname += ":" + str(result.rdata.port) + if kdc: + kdc += "," + qname + else: + kdc = qname + + return [realm, kdc] diff --git a/ipa-client/ipaclient/ntpconf.py b/ipa-client/ipaclient/ntpconf.py new file mode 100644 index 00000000..14e720c2 --- /dev/null +++ b/ipa-client/ipaclient/ntpconf.py @@ -0,0 +1,111 @@ +# Authors: Karl MacMillan <kmacmillan@redhat.com> +# +# Copyright (C) 2007 Red Hat +# see file 'COPYING' for use and warranty information +# +# 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; version 2 only +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +from ipa.ipautil import * +import shutil + +ntp_conf = """# Permit time synchronization with our time source, but do not +# permit the source to query or modify the service on this system. +restrict default kod nomodify notrap nopeer noquery +restrict -6 default kod nomodify notrap nopeer noquery + +# Permit all access over the loopback interface. This could +# be tightened as well, but to do so would effect some of +# the administrative functions. +restrict 127.0.0.1 +restrict -6 ::1 + +# Hosts on local network are less restricted. +#restrict 192.168.1.0 mask 255.255.255.0 nomodify notrap + +# Use public servers from the pool.ntp.org project. +# Please consider joining the pool (http://www.pool.ntp.org/join.html). +server $SERVER + +#broadcast 192.168.1.255 key 42 # broadcast server +#broadcastclient # broadcast client +#broadcast 224.0.1.1 key 42 # multicast server +#multicastclient 224.0.1.1 # multicast client +#manycastserver 239.255.254.254 # manycast server +#manycastclient 239.255.254.254 key 42 # manycast client + +# Undisciplined Local Clock. This is a fake driver intended for backup +# and when no outside source of synchronized time is available. +server 127.127.1.0 # local clock +#fudge 127.127.1.0 stratum 10 + +# Drift file. Put this in a directory which the daemon can write to. +# No symbolic links allowed, either, since the daemon updates the file +# by creating a temporary in the same directory and then rename()'ing +# it to the file. +driftfile /var/lib/ntp/drift + +# Key file containing the keys and key identifiers used when operating +# with symmetric key cryptography. +keys /etc/ntp/keys + +# Specify the key identifiers which are trusted. +#trustedkey 4 8 42 + +# Specify the key identifier to use with the ntpdc utility. +#requestkey 8 + +# Specify the key identifier to use with the ntpq utility. +#controlkey 8 +""" + +ntp_sysconfig = """# Drop root to id 'ntp:ntp' by default. +OPTIONS="-x -u ntp:ntp -p /var/run/ntpd.pid" + +# Set to 'yes' to sync hw clock after successful ntpdate +SYNC_HWCLOCK=yes + +# Additional options for ntpdate +NTPDATE_OPTIONS="" +""" + +def config_ntp(server_fqdn, fstore = None): + sub_dict = { } + sub_dict["SERVER"] = server_fqdn + + nc = template_str(ntp_conf, sub_dict) + + if fstore: + fstore.backup_file("/etc/ntp.conf") + else: + shutil.copy("/etc/ntp.conf", "/etc/ntp.conf.ipasave") + + fd = open("/etc/ntp.conf", "w") + fd.write(nc) + fd.close() + + if fstore: + fstore.backup_file("/etc/sysconfig/ntpd") + else: + shutil.copy("/etc/sysconfig/ntpd", "/etc/sysconfig/ntpd.ipasave") + + fd = open("/etc/sysconfig/ntpd", "w") + fd.write(ntp_sysconfig) + fd.close() + + # Set the ntpd to start on boot + run(["/sbin/chkconfig", "ntpd", "on"]) + + # Restart ntpd + run(["/sbin/service", "ntpd", "restart"]) diff --git a/ipa-client/man/Makefile.am b/ipa-client/man/Makefile.am new file mode 100644 index 00000000..7d0a3aa4 --- /dev/null +++ b/ipa-client/man/Makefile.am @@ -0,0 +1,16 @@ +# This file will be processed with automake-1.7 to create Makefile.in + +AUTOMAKE_OPTIONS = 1.7 + +NULL = + +man_MANS = \ + ipa-getkeytab.1 \ + ipa-client-install.1 + +install-data-hook: + @for i in $(man_MANS) ; do gzip -f $(DESTDIR)$(man1dir)/$$i ; done + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) diff --git a/ipa-client/man/ipa-client-install.1 b/ipa-client/man/ipa-client-install.1 new file mode 100644 index 00000000..49595a06 --- /dev/null +++ b/ipa-client/man/ipa-client-install.1 @@ -0,0 +1,55 @@ +.\" A man page for ipa-client-install +.\" Copyright (C) 2008 Red Hat, Inc. +.\" +.\" This is free software; you can redistribute it and/or modify it under +.\" the terms of the GNU Library General Public License as published by +.\" the Free Software Foundation; version 2 only +.\" +.\" 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 Library General Public +.\" License along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" Author: Rob Crittenden <rcritten@redhat.com> +.\" +.TH "ipa-client-install" "1" "Mar 14 2008" "freeipa" "" +.SH "NAME" +ipa\-client\-install \- Configure an IPA client +.SH "SYNOPSIS" +ipa\-client\-install [\fIOPTION\fR]... +.SH "DESCRIPTION" +Configures a client machine to use IPA for authentication and identity services. + +This configures PAM and NSS (Name Switching Service) to work with an IPA server over Kerberos and LDAP. +.SH "OPTIONS" +.TP +\fB\-\-domain\fR=\fIDOMAIN\fR +Set the domain name to DOMAIN +.TP +\fB\-\-server\fR=\fISERVER\fR +Set the IPA server to connect to +.TP +\fB\-\-realm\fR=\fIREALM_NAME\fR +Set the IPA realm name to REALM_NAME +.TP +\fB\-f\fR, \fB\-\-force\fR +Force the settings even if errors occur +.TP +\fB\-d\fR, \fB\-\-debug\fR +Print debugging information to stdout +.TP +\fB\-U\fR, \fB\-\-unattended\fR +Unattended installation. The user will not be prompted. +.TP +\fB\-N\fR, \fB\-\-no\-ntp\fR +Do not configure or enable NTP +\fB\-\-on\-master\fB +The client is being configured on an IPA server +.SH "EXIT STATUS" +0 if the installation was successful + +1 if an error occurred diff --git a/ipa-client/man/ipa-getkeytab.1 b/ipa-client/man/ipa-getkeytab.1 new file mode 100644 index 00000000..93db094e --- /dev/null +++ b/ipa-client/man/ipa-getkeytab.1 @@ -0,0 +1,101 @@ +.\" A man page for ipa-getkeytab +.\" Copyright (C) 2007 Red Hat, Inc. +.\" +.\" This is free software; you can redistribute it and/or modify it under +.\" the terms of the GNU Library General Public License as published by +.\" the Free Software Foundation; version 2 only +.\" +.\" 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 Library General Public +.\" License along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" Author: Karl MacMillan <kmacmill@redhat.com> +.\" Author: Simo Sorce <ssorce@redhat.com> +.\" +.TH "ipa-getkeytab" "1" "Oct 10 2007" "freeipa" "" +.SH "NAME" +ipa\-getkeytab \- Get a keytab for a kerberos principal +.SH "SYNOPSIS" +ipa\-getkeytab [ \fB\-s\fR ipaserver ] [ \fB\-p\fR principal\-name ] [ \fB\-k\fR keytab\-file ] [ \fB\-e\fR encryption\-types ] [ \fB\-q\fR ] + +.SH "DESCRIPTION" +Retrieves a kerberos \fIkeytab\fR. + +Kerberos keytabs are used for services (like sshd) to +perform kerberos authentication. A keytab is a file +with one or more secrets (or keys) for a kerberos +principal. + +A kerberos service principal is a kerberos identity +that can be used for authentication. Service principals +contain the name of the service, the hostname of the +server, and the realm name. For example, the following +is an example principal for an ldap server: + + ldap/foo.example.com@EXAMPLE.COM + +When using ipa\-getkeytab the realm name is already +provided, so the principal name is just the service +name and hostname (ldap/foo.example.com from the +example above). + +\fBWARNING:\fR retrieving the keytab resets the secret for the Kerberos principal. +This renders all other keytabs for that principal invalid. +.SH "OPTIONS" +.TP +\fB\-s ipaserver\fR +The IPA server to retrieve the keytab from (FQDN). +.TP +\fB\-p principal\-name\fR +The non\-realm part of the full principal name. +.TP +\fB\-k keytab\-file\fR +The keytab file where to append the new key (will be +created if it does not exist). +.TP +\fB\-e encryption\-types\fR +The list of encryption types to use to generate keys. +ipa\-getkeytab will use local client defaults if not provided. +Valid values depend on the kerberos library version and configuration. +Common values are: +aes256\-cts +aes128\-cts +des3\-hmac\-sha1 +arcfour\-hmac +des\-hmac\-sha1 +des\-cbc\-md5 +des\-cbc\-crc +.TP +\fB\-q\fR +Quiet mode. Only errors are displayed. +.TP +\fB\-\-permitted\-enctypes\fR +This options returns a description of the permitted encryption types, like this: +Supported encryption types: +AES\-256 CTS mode with 96\-bit SHA\-1 HMAC +AES\-128 CTS mode with 96\-bit SHA\-1 HMAC +Triple DES cbc mode with HMAC/sha1 +ArcFour with HMAC/md5 +DES cbc mode with CRC\-32 +DES cbc mode with RSA\-MD5 +DES cbc mode with RSA\-MD4 +.SH "EXAMPLES" +Add and retrieve a keytab for the NFS service principal on +the host foo.example.com and save it in the file /tmp/nfs.keytab and retrieve just the des\-cbc\-crc key. + + # ipa\-getkeytab \-s ipaserver.example.com \-p nfs/foo.example.com \-k /tmp/nfs.keytab \-e des\-cbc\-crc + +Add and retrieve a keytab for the ldap service principal on +the host foo.example.com and save it in the file /tmp/ldap.keytab. + + # ipa\-getkeytab \-s ipaserver.example.com \-p ldap/foo.example.com \-k /tmp/ldap.keytab + + + +.SH "EXIT STATUS" +The exit status is 0 on success, nonzero on error. diff --git a/ipa-client/version.m4.in b/ipa-client/version.m4.in new file mode 100644 index 00000000..5ddc8cea --- /dev/null +++ b/ipa-client/version.m4.in @@ -0,0 +1 @@ +define([IPA_VERSION], [__VERSION__]) |