summaryrefslogtreecommitdiffstats
path: root/ipa-client
diff options
context:
space:
mode:
Diffstat (limited to 'ipa-client')
-rw-r--r--ipa-client/AUTHORS0
-rw-r--r--ipa-client/Makefile.am100
-rw-r--r--ipa-client/NEWS0
-rw-r--r--ipa-client/README24
-rwxr-xr-xipa-client/autogen.sh196
-rw-r--r--ipa-client/configure.ac195
-rw-r--r--ipa-client/firefox/Makefile.am18
-rw-r--r--ipa-client/firefox/README23
-rw-r--r--ipa-client/firefox/globalsetup.sh52
-rw-r--r--ipa-client/firefox/ipa.cfg19
-rw-r--r--ipa-client/firefox/ipa.js34
-rw-r--r--ipa-client/firefox/usersetup.sh40
-rw-r--r--ipa-client/ipa-client.spec.in86
-rw-r--r--ipa-client/ipa-getkeytab.c836
-rw-r--r--ipa-client/ipa-install/Makefile.am13
-rw-r--r--ipa-client/ipa-install/ipa-client-install387
-rw-r--r--ipa-client/ipaclient/Makefile.am16
-rw-r--r--ipa-client/ipaclient/__init__.py21
-rw-r--r--ipa-client/ipaclient/ipachangeconf.py459
-rw-r--r--ipa-client/ipaclient/ipadiscovery.py248
-rw-r--r--ipa-client/ipaclient/ntpconf.py111
-rw-r--r--ipa-client/man/Makefile.am16
-rw-r--r--ipa-client/man/ipa-client-install.155
-rw-r--r--ipa-client/man/ipa-getkeytab.1101
-rw-r--r--ipa-client/version.m4.in1
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__])