Significant changes for nfs-utils 1.1.0 - March/April 2007
- rpc.lockd is gone. One 3 old kernel releases need it.
- - /sbin/{u,}mount.nfs{,4} is now installed so 'mount' will
+ - /sbin/{u,}mount.nfs{,4} are now installed so 'mount' will
use these to mount nfs filesystems instead of internal code.
+ mount.nfs will check for 'statd' to be running when mounting
a filesystem which requires it. If it is not running it will
if you kill and restart it, it will restore that state and
continue working correctly.
+ statd makes more use of DNS lookup and should handle
- multi-homed peers better.
+ multi-homed peers better. In particular, files in
+ /var/lib/nfs/sm/ are named with the Full Qualified Domain Name
+ if available.
- If you export a directory as 'crossmnt', all filesystems
mounted beneath are automatically exported with the same
options (unless explicitly exported with different options).
no_subtree_check.
- By default the system 'rpcgen' is used while building
nfs-utils rather than the internal one.
+ - Exportfs will warn if you try to export a filesystem that does
+ not support NFS export.
+ - Comprehensive notes on startup dependencies have been added
+ to the README file.
+ - Mount and statd now listen on a non-privileged port by default.
+ For maximum safety an upgrade to portmap is recommended.
+ git://neil.brown.name/portmap
+ - This release should work with MIT Kerberos and Heimdal 0.8.1 and later.
-Further notes on statd:
- statd should be installed in /usr/sbin, not /sbin.
- If you need to mount /usr via nfs, use 'nolock'
+ - A new option, -n, was added to rpc.gssd which specifies that
+ accesses by root should not use 'machine credentials' when
+ accessing NFS file systems mounted with Kerberos. Using this
+ option allows the root user to access the NFS space using any
+ Kerberos principal, rather than always using the machine
+ credentials. However, its use also requires that root manually
+ authenticate before attempting a mount with Kerberos.
- At boot time, run "/usr/sbin/sm-notify".
- Run "statd" only when starting the NFS server.
- "statd" should be run before starting the NFS server.
- You do not need to start statd at boot time incase an
- NFS filesystem is mounted. mount.nfs will take care of that.
+ When rpc.gssd uses machine credentials, the selection algorithm has
+ been changed. Instead of simply using the first "nfs/*" key in the
+ keytab, the keytab is now searched for keys in the following
+ defined order:
- Make sure /usr/sbin/start-statd will run statd with required
- arguments.
+ root/<fqdn>@REALM
+ nfs/<fqdn>@REALM
+ host/<fqdn>@REALM
+ root/<any-name>@REALM
+ nfs/<any-name>@REALM
+ host/<any-name>@REALM
Finally, build as usual as above.
+3. DAEMON STARTUP ORDER
+
+This nfs-utils packages does not provide any scripts for starting
+various daemons as most distributions replace them with their own, so
+any scripts we package would not get much testing.
+Instead, we explain the dependencies involved in startup so that
+scripts can be written to work correctly.
+
+3.0 PREREQUISITES
+
+ Name service (host name lookup) should be working before any
+ NFS services are started.
+
+ "portmap" must be running before any NFS services (server or
+ client) are started.
+
+ Normally network interfaces should be configured first as well,
+ though this isn't critical for the NFS server (providing name
+ service is handled locally).
+
+3.1. SERVER STARTUP
+
+
+ A/ mount -t nfsd /proc/fs/nfsd
+ This filesystem needs to be mount before most daemons,
+ particularly exportfs, mountd, svcgssd, idmapd.
+ It could be mounted once, or the script that starts each daemon
+ could test if it is mounted and mount it if not.
+
+ B/ svcgssd ; idmapd
+ These supply services to nfsd and so should be started before
+ rpc.nfsd. Where they come between mounting the nfsd filesystem
+ and starting the nfsd server is not important.
+ idmapd is only needed for NFSv4 support.
+ svcgssd is only needed if exportfs NFS filesystem with crypto-
+ security (Kerberos or SPKM3).
+
+ C/ exportfs -av ; rpc.mountd
+ It is important that exportfs be run before mountd so that
+ mountd is working from current information (in
+ /var/lib/nfs/etab).
+ It is also important that both of these are run before
+ rpc.nfsd.
+ If not, any NFS requests that arrive before mountd is started
+ will get replied to with a 'Stale NFS File handle' error.
+
+ D/ rpc.statd --no-notify
+ It is best if statd is started before nfsd though this isn't
+ critical. Certainly it should be at most a few seconds after
+ nfsd.
+ When nfsd starts it will start lockd. If lockd then receives a
+ lock request it will communicate with statd. If statd is not
+ running lockd will retry, but it won't wait forever for a
+ reply.
+ Note that if statd is started before nfsd, the --no-notify
+ option must be used. If notify requests are sent out before
+ nfsd start, clients may try to reclaim locks and, on finding
+ that lockd isn't running, they will give up and never reclaim
+ the lock.
+ rpc.statd is only needed for NFSv2 and NFSv3 support.
+
+ E/ rpc.nfsd
+ Starting nfsd will automatically start lockd. The nfs server
+ will now be fully active and respond to any requests from
+ clients.
+
+ F/ sm-notify
+ This will notify any client which might have locks from before
+ a reboot to try to reclaim their locks. This should start
+ immediately after rpc.nfsd is started so that clients have a
+ chance to reclaim locks within the 90 second grace period.
+ sm-notify is only needed for NFSv2 and NFSv3 support.
+
+
+3.2. CLIENT STARTUP
+
+ A/ sm-notify
+ This should be run shortly after boot and before any NFS
+ filesystems are mounted with remote-locking support -
+ filesystems can be mounted with "-o nolock" before sm-notify.
+ This is appropriate for '/', '/usr', and '/var'.
+
+ B/ gssd ; idmapd
+ idmapd should be started before mounting any NFSv4 filesystems.
+ gssd should be started before mounting any NFS filesystems
+ securely (with Kerberos of SPKM3).
+
+ C/ statd should be run before any NFSv2 or NFSv3 filesystem is
+ mounted with remote locking (i.e. without -o nolock).
+ 'mount' will try to use "/usr/sbin/start-statd" to start statd
+ if it is not already running, so there is no need to explicitly
+ start statd in boot-time scripts.
+
+3.3. SERVER/CLIENT INTERACTIONS
+
+ A/ sm-notify
+ Both the server and the client need sm-notify to be run.
+ It should be run after the NFS server is started, but before
+ and NFS filesystems are mounted with remote locking.
+
+ B/ rpc.statd
+ Both the server and the client need rpc.statd to be running.
+ Each should try to start when they need it.
+
+ C/ idmapd
+
+ Both the server and client need idmapd to be running. If idmapd
+ is started (for the client) before starting nfsd the 'nfsd'
+ filesystem is mounted, then idmapd should be sent a HUP signal
+ afterwards to signal that the server channels should be opened.
+
+
+
+
Share And Enjoy!
-- the nfs-utils developers
AC_CHECK_LIB($gssapi_lib, gss_krb5_ccache_name,
AC_DEFINE(HAVE_GSS_KRB5_CCACHE_NAME, 1, [Define this if the Kerberos GSS library supports gss_krb5_ccache_name]), ,$KRBLIBS)
+ dnl Check for newer error message facility
+ AC_CHECK_LIB($gssapi_lib, krb5_get_error_message,
+ AC_DEFINE(HAVE_KRB5_GET_ERROR_MESSAGE, 1, [Define this if the function krb5_get_error_message is available]), ,$KRBLIBS)
+
dnl If they specified a directory and it didn't work, give them a warning
if test "x$krb5_with" != "x" -a "$krb5_with" != "$KRBDIR"; then
AC_MSG_WARN(Using $KRBDIR instead of requested value of $krb5_with for Kerberos!)
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.61 for linux nfs-utils 1.1.0-rc1.
+# Generated by GNU Autoconf 2.61 for linux nfs-utils 1.1.0-rc2.
#
# Report bugs to <nfs@lists.sf.net>.
#
# Identity of this package.
PACKAGE_NAME='linux nfs-utils'
PACKAGE_TARNAME='nfs-utils'
-PACKAGE_VERSION='1.1.0-rc1'
-PACKAGE_STRING='linux nfs-utils 1.1.0-rc1'
+PACKAGE_VERSION='1.1.0-rc2'
+PACKAGE_STRING='linux nfs-utils 1.1.0-rc2'
PACKAGE_BUGREPORT='nfs@lists.sf.net'
ac_default_prefix=/usr
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures linux nfs-utils 1.1.0-rc1 to adapt to many kinds of systems.
+\`configure' configures linux nfs-utils 1.1.0-rc2 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of linux nfs-utils 1.1.0-rc1:";;
+ short | recursive ) echo "Configuration of linux nfs-utils 1.1.0-rc2:";;
esac
cat <<\_ACEOF
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-linux nfs-utils configure 1.1.0-rc1
+linux nfs-utils configure 1.1.0-rc2
generated by GNU Autoconf 2.61
Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by linux nfs-utils $as_me 1.1.0-rc1, which was
+It was created by linux nfs-utils $as_me 1.1.0-rc2, which was
generated by GNU Autoconf 2.61. Invocation command line was
$ $0 $@
# Define the identity of the package.
PACKAGE='nfs-utils'
- VERSION='1.1.0-rc1'
+ VERSION='1.1.0-rc2'
cat >>confdefs.h <<_ACEOF
pkg_cv_GSSAPI_CFLAGS="$GSSAPI_CFLAGS"
else
if test -n "$PKG_CONFIG" && \
- { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"libgssapi >= 0.9\"") >&5
- ($PKG_CONFIG --exists --print-errors "libgssapi >= 0.9") 2>&5
+ { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"libgssapi >= 0.11\"") >&5
+ ($PKG_CONFIG --exists --print-errors "libgssapi >= 0.11") 2>&5
ac_status=$?
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); }; then
- pkg_cv_GSSAPI_CFLAGS=`$PKG_CONFIG --cflags "libgssapi >= 0.9" 2>/dev/null`
+ pkg_cv_GSSAPI_CFLAGS=`$PKG_CONFIG --cflags "libgssapi >= 0.11" 2>/dev/null`
else
pkg_failed=yes
fi
pkg_cv_GSSAPI_LIBS="$GSSAPI_LIBS"
else
if test -n "$PKG_CONFIG" && \
- { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"libgssapi >= 0.9\"") >&5
- ($PKG_CONFIG --exists --print-errors "libgssapi >= 0.9") 2>&5
+ { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"libgssapi >= 0.11\"") >&5
+ ($PKG_CONFIG --exists --print-errors "libgssapi >= 0.11") 2>&5
ac_status=$?
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); }; then
- pkg_cv_GSSAPI_LIBS=`$PKG_CONFIG --libs "libgssapi >= 0.9" 2>/dev/null`
+ pkg_cv_GSSAPI_LIBS=`$PKG_CONFIG --libs "libgssapi >= 0.11" 2>/dev/null`
else
pkg_failed=yes
fi
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
- GSSAPI_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "libgssapi >= 0.9"`
+ GSSAPI_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "libgssapi >= 0.11"`
else
- GSSAPI_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "libgssapi >= 0.9"`
+ GSSAPI_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "libgssapi >= 0.11"`
fi
# Put the nasty error message in config.log where it belongs
echo "$GSSAPI_PKG_ERRORS" >&5
- { { echo "$as_me:$LINENO: error: Package requirements (libgssapi >= 0.9) were not met:
+ { { echo "$as_me:$LINENO: error: Package requirements (libgssapi >= 0.11) were not met:
$GSSAPI_PKG_ERRORS
and GSSAPI_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.
" >&5
-echo "$as_me: error: Package requirements (libgssapi >= 0.9) were not met:
+echo "$as_me: error: Package requirements (libgssapi >= 0.11) were not met:
$GSSAPI_PKG_ERRORS
#define HAVE_GSS_KRB5_CCACHE_NAME 1
_ACEOF
+fi
+
+
+ as_ac_Lib=`echo "ac_cv_lib_$gssapi_lib''_krb5_get_error_message" | $as_tr_sh`
+{ echo "$as_me:$LINENO: checking for krb5_get_error_message in -l$gssapi_lib" >&5
+echo $ECHO_N "checking for krb5_get_error_message in -l$gssapi_lib... $ECHO_C" >&6; }
+if { as_var=$as_ac_Lib; eval "test \"\${$as_var+set}\" = set"; }; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-l$gssapi_lib $KRBLIBS $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char krb5_get_error_message ();
+int
+main ()
+{
+return krb5_get_error_message ();
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+ (eval "$ac_link") 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } && {
+ test -z "$ac_c_werror_flag" ||
+ test ! -s conftest.err
+ } && test -s conftest$ac_exeext &&
+ $as_test_x conftest$ac_exeext; then
+ eval "$as_ac_Lib=yes"
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ eval "$as_ac_Lib=no"
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+ac_res=`eval echo '${'$as_ac_Lib'}'`
+ { echo "$as_me:$LINENO: result: $ac_res" >&5
+echo "${ECHO_T}$ac_res" >&6; }
+if test `eval echo '${'$as_ac_Lib'}'` = yes; then
+
+cat >>confdefs.h <<\_ACEOF
+#define HAVE_KRB5_GET_ERROR_MESSAGE 1
+_ACEOF
+
fi
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by linux nfs-utils $as_me 1.1.0-rc1, which was
+This file was extended by linux nfs-utils $as_me 1.1.0-rc2, which was
generated by GNU Autoconf 2.61. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF
ac_cs_version="\\
-linux nfs-utils config.status 1.1.0-rc1
+linux nfs-utils config.status 1.1.0-rc2
configured by $0, generated by GNU Autoconf 2.61,
with options \\"`echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\"
dnl Process this file with autoconf to produce a configure script.
dnl
-AC_INIT([linux nfs-utils],[1.1.0-rc1],[nfs@lists.sf.net],[nfs-utils])
+AC_INIT([linux nfs-utils],[1.1.0-rc2],[nfs@lists.sf.net],[nfs-utils])
AC_CANONICAL_BUILD([])
AC_CANONICAL_HOST([])
AC_CONFIG_MACRO_DIR(aclocal)
[AC_MSG_ERROR([Unable to locate information required to use librpcsecgss. If you have pkgconfig installed, you might try setting environment variable PKG_CONFIG_PATH to /usr/local/lib/pkgconfig])
]
)
- PKG_CHECK_MODULES(GSSAPI, libgssapi >= 0.9)
+ PKG_CHECK_MODULES(GSSAPI, libgssapi >= 0.11)
fi
fi
/* Define this if you have MIT Kerberos libraries */
#undef HAVE_KRB5
+/* Define this if the function krb5_get_error_message is available */
+#undef HAVE_KRB5_GET_ERROR_MESSAGE
+
/* Define to 1 if you have the <libintl.h> header file. */
#undef HAVE_LIBINTL_H
}
else
{
- if (bindresvport (sock, &addr))
- {
addr.sin_port = 0;
if (bind (sock, (struct sockaddr *) &addr, len) < 0)
{
(void) __close (sock);
sock = -1;
}
- }
}
if (sock >= 0)
#include <config.h>
#endif
+#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
static void dump(int verbose);
static void error(nfs_export *exp, int err);
static void usage(void);
-
+static void validate_export(nfs_export *exp);
int
main(int argc, char **argv)
exp->m_mayexport = 1;
exp->m_changed = 1;
exp->m_warned = 0;
+ validate_export(exp);
}
}
}
exp->m_mayexport = 1;
exp->m_changed = 1;
exp->m_warned = 0;
+ validate_export(exp);
if (hp) free (hp);
}
if (hp) free (hp);
}
+static int can_test(void)
+{
+ int fd;
+ int n;
+ char *setup = "nfsd 0.0.0.0 2147483647 -test-client-\n";
+ fd = open("/proc/net/rpc/auth.unix.ip/channel", O_WRONLY);
+ if ( fd < 0) return 0;
+ n = write(fd, setup, strlen(setup));
+ close(fd);
+ if (n < 0)
+ return 0;
+ fd = open("/proc/net/rpc/nfsd.export/channel", O_WRONLY);
+ if ( fd < 0) return 0;
+ close(fd);
+ return 1;
+}
+
+static int test_export(char *path, int with_fsid)
+{
+ char buf[1024];
+ int fd, n;
+
+ sprintf(buf, "-test-client- %s 3 %d -1 -1 0\n",
+ path,
+ with_fsid ? NFSEXP_FSID : 0);
+ fd = open("/proc/net/rpc/nfsd.export/channel", O_WRONLY);
+ if (fd < 0)
+ return 0;
+ n = write(fd, buf, strlen(buf));
+ close(fd);
+ if (n < 0)
+ return 0;
+ return 1;
+}
+
+static void
+validate_export(nfs_export *exp)
+{
+ /* Check that the given export point is potentially exportable.
+ * We just give warnings here, don't cause anything to fail.
+ * If a path doesn't exist, or is not a dir or file, give an warning
+ * otherwise trial-export to '-test-client-' and check for failure.
+ */
+ struct stat stb;
+ char *path = exp->m_export.e_path;
+
+ if (stat(path, &stb) < 0) {
+ fprintf(stderr, "exportfs: Warning: %s does not exist\n",
+ path);
+ return;
+ }
+ if (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode)) {
+ fprintf(stderr, "exportfs: Warning: %s is neither "
+ "a directory nor a file.\n"
+ " remote access will fail\n", path);
+ return;
+ }
+ if (!can_test())
+ return;
+
+ if ((exp->m_export.e_flags & NFSEXP_FSID) || exp->m_export.e_uuid) {
+ if ( !test_export(path, 1)) {
+ fprintf(stderr, "exportfs: Warning: %s does not "
+ "support NFS export.\n",
+ path);
+ return;
+ }
+ } else if ( ! test_export(path, 0)) {
+ if (test_export(path, 1))
+ fprintf(stderr, "exportfs: Warning: %s requires fsid= "
+ "for NFS export\n", path);
+ else
+ fprintf(stderr, "exportfs: Warning: %s does not "
+ "support NFS export.\n",
+ path);
+ return;
+
+ }
+}
+
+
static char
dumpopt(char c, char *fmt, ...)
{
if ((ret = krb5_init_context(&context))) {
printerr(0, "ERROR: initializing krb5_context: %s\n",
- error_message(ret));
+ gssd_k5_err_msg(NULL, ret));
goto out_err;
}
if ((ret = krb5_auth_con_getlocalsubkey(context,
ctx->auth_context, &key))){
printerr(0, "ERROR: getting auth_context key: %s\n",
- error_message(ret));
+ gssd_k5_err_msg(context, ret));
goto out_err_free_context;
}
calloc(1, enc_key.keyvalue.length)) == NULL) {
printerr(0, "ERROR: allocating memory for enc key: %s\n",
- error_message(ENOMEM));
+ gssd_k5_err_msg(context, ENOMEM));
goto out_err_free_key;
}
skd = (char *) key->keyvalue.data;
if ((ret = krb5_init_context(&context))) {
printerr(0, "ERROR: initializing krb5_context: %s\n",
- error_message(ret));
+ gssd_k5_err_msg(NULL, ret));
goto out_err;
}
if ((ret = krb5_auth_con_getlocalsubkey(context,
ctx->auth_context, &key))){
printerr(0, "ERROR: getting auth_context key: %s\n",
- error_message(ret));
+ gssd_k5_err_msg(context, ret));
goto out_err_free_context;
}
signal(SIGTERM, sig_die);
signal(SIGHUP, sig_hup);
- /* Process keytab file and get machine credentials */
- if (root_uses_machine_creds)
- gssd_refresh_krb5_machine_creds();
-
gssd_run();
printerr(0, "gssd_run returned!\n");
abort();
.I keytab
to obtain "machine credentials".
The default value is "/etc/krb5.keytab".
+.IP
Previous versions of
.B rpc.gssd
used only "nfs/*" keys found within the keytab.
-Now, the first keytab entry for each distinct Kerberos realm
-within the keytab is used. This means that an NFS client
-no longer needs an "nfs/hostname" principal and keytab entry,
-but can instead use a "host/hostname" (or any other) keytab
-entry that is available.
+To be more consistent with other implementations, we now look for
+specific keytab entries. The search order for keytabs to be used
+for "machine credentials" is now:
+.br
+ root/<hostname>@<REALM>
+.br
+ nfs/<hostname>@<REALM>
+.br
+ host/<hostname>@<REALM>
+.br
+ root/<anyname>@<REALM>
+.br
+ nfs/<anyname>@<REALM>
+.br
+ host/<anyname>@<REALM>
.TP
.B -p path
Tells
init_client_list();
+ printerr(1, "beginning poll\n");
while (1) {
while (dir_changed) {
dir_changed = 0;
ai_hints.ai_protocol = IPPROTO_UDP;
} else {
printerr(0, "WARNING: unrecognized protocol, '%s', requested "
- "for connection to server %s for user with uid %d",
+ "for connection to server %s for user with uid %d\n",
clp->protocol, clp->servername, uid);
goto out_fail;
}
/* extract the service name from clp->servicename */
if ((at_sign = strchr(clp->servicename, '@')) == NULL) {
printerr(0, "WARNING: servicename (%s) not formatted as "
- "expected with service@host", clp->servicename);
+ "expected with service@host\n", clp->servicename);
goto out_fail;
}
if ((at_sign - clp->servicename) >= sizeof(service)) {
printerr(0, "WARNING: service portion of servicename (%s) "
- "is too long!", clp->servicename);
+ "is too long!\n", clp->servicename);
goto out_fail;
}
strncpy(service, clp->servicename, at_sign - clp->servicename);
errcode = getaddrinfo(clp->servername, service, &ai_hints, &a);
if (errcode) {
printerr(0, "WARNING: Error from getaddrinfo for server "
- "'%s': %s", clp->servername, gai_strerror(errcode));
+ "'%s': %s\n", clp->servername, gai_strerror(errcode));
goto out_fail;
}
if (a == NULL) {
printerr(0, "WARNING: No address information found for "
- "connection to server %s for user with uid %d",
+ "connection to server %s for user with uid %d\n",
clp->servername, uid);
goto out_fail;
}
} else {
/* Shouldn't happen! */
printerr(0, "ERROR: requested protocol '%s', but "
- "got addrinfo with protocol %d",
+ "got addrinfo with protocol %d\n",
clp->protocol, a->ai_protocol);
goto out_fail;
}
if (uid == 0 && root_uses_machine_creds == 1) {
int success = 0;
+ gssd_refresh_krb5_machine_credential(clp->servername,
+ NULL);
/*
* Get a list of credential cache names and try each
* of them until one works or we've tried them all
*/
if (gssd_get_krb5_machine_cred_list(&credlist)) {
- printerr(0, "WARNING: Failed to obtain machine "
- "credentials for connection to "
- "server %s\n", clp->servername);
+ printerr(0, "ERROR: No credentials found "
+ "for connection to server %s\n",
+ clp->servername);
goto out_return_error;
}
for (ccname = credlist; ccname && *ccname; ccname++) {
#include <stdio.h>
#include <stdlib.h>
+#include <unistd.h>
#include <string.h>
#include <dirent.h>
+#include <netdb.h>
+#include <ctype.h>
#include <errno.h>
#include <time.h>
#include <gssapi/gssapi.h>
static int gssd_find_existing_krb5_ccache(uid_t uid, struct dirent **d);
static int gssd_get_single_krb5_cred(krb5_context context,
krb5_keytab kt, struct gssd_k5_kt_princ *ple);
-static int gssd_have_realm_ple(void *realm);
-static int gssd_process_krb5_keytab(krb5_context context, krb5_keytab kt,
- char *kt_name);
+
/*
* Called from the scandir function to weed out potential krb5
if (maj_stat != GSS_S_COMPLETE) {
pgsserr("gss_set_allowable_enctypes",
maj_stat, min_stat, &krb5oid);
+ gss_release_cred(&min_stat, &credh);
return -1;
}
sec->cred = credh;
int code;
time_t now = time(0);
char *cache_type;
+ char *pname = NULL;
memset(&my_creds, 0, sizeof(my_creds));
goto out;
}
+ if ((krb5_unparse_name(context, ple->princ, &pname)))
+ pname = NULL;
+
krb5_get_init_creds_opt_init(&options);
krb5_get_init_creds_opt_set_address_list(&options, NULL);
printerr(0, "WARNING: Using (debug) short machine cred lifetime!\n");
krb5_get_init_creds_opt_set_tkt_life(&options, 5*60);
#endif
- if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
- kt, 0, NULL, &options))) {
- char *pname;
- if ((krb5_unparse_name(context, ple->princ, &pname))) {
- pname = NULL;
- }
+ if ((code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ,
+ kt, 0, NULL, &options))) {
printerr(0, "WARNING: %s while getting initial ticket for "
- "principal '%s' from keytab '%s'\n",
- error_message(code),
+ "principal '%s' using keytab '%s'\n",
+ gssd_k5_err_msg(context, code),
pname ? pname : "<unparsable>", kt_name);
-#ifdef HAVE_KRB5
- if (pname) krb5_free_unparsed_name(context, pname);
-#else
- if (pname) free(pname);
-#endif
goto out;
}
GSSD_DEFAULT_CRED_DIR, GSSD_DEFAULT_CRED_PREFIX,
GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm);
ple->endtime = my_creds.times.endtime;
+ if (ple->ccname != NULL)
+ free(ple->ccname);
ple->ccname = strdup(cc_name);
if (ple->ccname == NULL) {
printerr(0, "ERROR: no storage to duplicate credentials "
- "cache name\n");
+ "cache name '%s'\n", cc_name);
code = ENOMEM;
goto out;
}
if ((code = krb5_cc_resolve(context, cc_name, &ccache))) {
printerr(0, "ERROR: %s while opening credential cache '%s'\n",
- error_message(code), cc_name);
+ gssd_k5_err_msg(context, code), cc_name);
goto out;
}
if ((code = krb5_cc_initialize(context, ccache, ple->princ))) {
printerr(0, "ERROR: %s while initializing credential "
- "cache '%s'\n", error_message(code), cc_name);
+ "cache '%s'\n", gssd_k5_err_msg(context, code),
+ cc_name);
goto out;
}
if ((code = krb5_cc_store_cred(context, ccache, &my_creds))) {
printerr(0, "ERROR: %s while storing credentials in '%s'\n",
- error_message(code), cc_name);
+ gssd_k5_err_msg(context, code), cc_name);
goto out;
}
code = 0;
- printerr(1, "Using (machine) credentials cache: '%s'\n", cc_name);
+ printerr(2, "Successfully obtained machine credentials for "
+ "principal '%s' stored in ccache '%s'\n", pname, cc_name);
out:
+ if (pname)
+ k5_free_unparsed_name(context, pname);
if (ccache)
krb5_cc_close(context, ccache);
krb5_free_cred_contents(context, &my_creds);
}
/*
- * Determine if we already have a ple for the given realm
- *
- * Returns:
- * 0 => no ple found for given realm
- * 1 => found ple for given realm
+ * Depending on the version of Kerberos, we either need to use
+ * a private function, or simply set the environment variable.
*/
-static int
-gssd_have_realm_ple(void *r)
+static void
+gssd_set_krb5_ccache_name(char *ccname)
+{
+#ifdef USE_GSS_KRB5_CCACHE_NAME
+ u_int maj_stat, min_stat;
+
+ printerr(2, "using gss_krb5_ccache_name to select krb5 ccache %s\n",
+ ccname);
+ maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
+ if (maj_stat != GSS_S_COMPLETE) {
+ printerr(0, "WARNING: gss_krb5_ccache_name with "
+ "name '%s' failed (%s)\n",
+ ccname, error_message(min_stat));
+ }
+#else
+ /*
+ * Set the KRB5CCNAME environment variable to tell the krb5 code
+ * which credentials cache to use. (Instead of using the private
+ * function above for which there is no generic gssapi
+ * equivalent.)
+ */
+ printerr(2, "using environment variable to select krb5 ccache %s\n",
+ ccname);
+ setenv("KRB5CCNAME", ccname, 1);
+#endif
+}
+
+/*
+ * Given a principal, find a matching ple structure
+ */
+static struct gssd_k5_kt_princ *
+find_ple_by_princ(krb5_context context, krb5_principal princ)
{
struct gssd_k5_kt_princ *ple;
+
+ for (ple = gssd_k5_kt_princ_list; ple != NULL; ple = ple->next) {
+ if (krb5_principal_compare(context, ple->princ, princ))
+ return ple;
+ }
+ /* no match found */
+ return NULL;
+}
+
+/*
+ * Create, initialize, and add a new ple structure to the global list
+ */
+static struct gssd_k5_kt_princ *
+new_ple(krb5_context context, krb5_principal princ)
+{
+ struct gssd_k5_kt_princ *ple = NULL, *p;
+ krb5_error_code code;
+ char *default_realm;
+ int is_default_realm = 0;
+
+ ple = malloc(sizeof(struct gssd_k5_kt_princ));
+ if (ple == NULL)
+ goto outerr;
+ memset(ple, 0, sizeof(*ple));
+
#ifdef HAVE_KRB5
- krb5_data *realm = (krb5_data *)r;
+ ple->realm = strndup(princ->realm.data,
+ princ->realm.length);
#else
- char *realm = (char *)r;
+ ple->realm = strdup(princ->realm);
#endif
+ if (ple->realm == NULL)
+ goto outerr;
+ code = krb5_copy_principal(context, princ, &ple->princ);
+ if (code)
+ goto outerr;
- for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
+ /*
+ * Add new entry onto the list (if this is the default
+ * realm, always add to the front of the list)
+ */
+
+ code = krb5_get_default_realm(context, &default_realm);
+ if (code == 0) {
+ if (strcmp(ple->realm, default_realm) == 0)
+ is_default_realm = 1;
+ k5_free_default_realm(context, default_realm);
+ }
+
+ if (is_default_realm) {
+ ple->next = gssd_k5_kt_princ_list;
+ gssd_k5_kt_princ_list = ple;
+ } else {
+ p = gssd_k5_kt_princ_list;
+ while (p != NULL && p->next != NULL)
+ p = p->next;
+ if (p == NULL)
+ gssd_k5_kt_princ_list = ple;
+ else
+ p->next = ple;
+ }
+
+ return ple;
+outerr:
+ if (ple) {
+ if (ple->realm)
+ free(ple->realm);
+ free(ple);
+ }
+ return NULL;
+}
+
+/*
+ * Given a principal, find an existing ple structure, or create one
+ */
+static struct gssd_k5_kt_princ *
+get_ple_by_princ(krb5_context context, krb5_principal princ)
+{
+ struct gssd_k5_kt_princ *ple;
+
+ /* Need to serialize list if we ever become multi-threaded! */
+
+ ple = find_ple_by_princ(context, princ);
+ if (ple == NULL) {
+ ple = new_ple(context, princ);
+ }
+
+ return ple;
+}
+
+/*
+ * Given a (possibly unqualified) hostname,
+ * return the fully qualified (lower-case!) hostname
+ */
+static int
+get_full_hostname(const char *inhost, char *outhost, int outhostlen)
+{
+ struct addrinfo *addrs = NULL;
+ struct addrinfo hints;
+ int retval;
+ char *c;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_flags = AI_CANONNAME;
+
+ /* Get full target hostname */
+ retval = getaddrinfo(inhost, NULL, &hints, &addrs);
+ if (retval) {
+ printerr(0, "%s while getting full hostname for '%s'\n",
+ gai_strerror(retval), inhost);
+ goto out;
+ }
+ strncpy(outhost, addrs->ai_canonname, outhostlen);
+ freeaddrinfo(addrs);
+ for (c = outhost; *c != '\0'; c++)
+ *c = tolower(*c);
+
+ printerr(3, "Full hostname for '%s' is '%s'\n", inhost, outhost);
+ retval = 0;
+out:
+ return retval;
+}
+
+/*
+ * If principal matches the given realm and service name,
+ * and has *any* instance (hostname), return 1.
+ * Otherwise return 0, indicating no match.
+ */
+static int
+realm_and_service_match(krb5_context context, krb5_principal p,
+ const char *realm, const char *service)
+{
#ifdef HAVE_KRB5
- if ((realm->length == strlen(ple->realm)) &&
- (strncmp(realm->data, ple->realm, realm->length) == 0)) {
+ /* Must have two components */
+ if (p->length != 2)
+ return 0;
+ if ((strlen(realm) == p->realm.length)
+ && (strncmp(realm, p->realm.data, p->realm.length) == 0)
+ && (strlen(service) == p->data[0].length)
+ && (strncmp(service, p->data[0].data, p->data[0].length) == 0))
+ return 1;
#else
- if (strcmp(realm, ple->realm) == 0) {
+ const char *name, *inst;
+
+ if (p->name.name_string.len != 2)
+ return 0;
+ name = krb5_principal_get_comp_string(context, p, 0);
+ inst = krb5_principal_get_comp_string(context, p, 1);
+ if (name == NULL || inst == NULL)
+ return 0;
+ if ((strcmp(realm, p->realm) == 0)
+ && (strcmp(service, name) == 0))
+ return 1;
#endif
- return 1;
- }
- }
return 0;
}
/*
- * Process the given keytab file and create a list of principals we
- * might use as machine credentials.
+ * Search the given keytab file looking for an entry with the given
+ * service name and realm, ignoring hostname (instance).
*
* Returns:
- * 0 => Sucess
- * nonzero => Error
+ * 0 => No error
+ * non-zero => An error occurred
+ *
+ * If a keytab entry is found, "found" is set to one, and the keytab
+ * entry is returned in "kte". Otherwise, "found" is zero, and the
+ * value of "kte" is unpredictable.
*/
static int
-gssd_process_krb5_keytab(krb5_context context, krb5_keytab kt, char *kt_name)
+gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt,
+ const char *realm, const char *service,
+ int *found, krb5_keytab_entry *kte)
{
krb5_kt_cursor cursor;
- krb5_keytab_entry kte;
krb5_error_code code;
struct gssd_k5_kt_princ *ple;
int retval = -1;
+ char kt_name[BUFSIZ];
+ char *pname;
+
+ if (found == NULL) {
+ retval = EINVAL;
+ goto out;
+ }
+ *found = 0;
/*
* Look through each entry in the keytab file and determine
* if we might want to use it as machine credentials. If so,
* save info in the global principal list (gssd_k5_kt_princ_list).
- * Note: (ple == principal list entry)
*/
+ if ((code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ))) {
+ printerr(0, "ERROR: %s attempting to get keytab name\n",
+ gssd_k5_err_msg(context, code));
+ retval = code;
+ goto out;
+ }
if ((code = krb5_kt_start_seq_get(context, kt, &cursor))) {
printerr(0, "ERROR: %s while beginning keytab scan "
"for keytab '%s'\n",
- error_message(code), kt_name);
+ gssd_k5_err_msg(context, code), kt_name);
retval = code;
goto out;
}
- while ((code = krb5_kt_next_entry(context, kt, &kte, &cursor)) == 0) {
- char *pname;
- if ((code = krb5_unparse_name(context, kte.principal,
+ while ((code = krb5_kt_next_entry(context, kt, kte, &cursor)) == 0) {
+ if ((code = krb5_unparse_name(context, kte->principal,
&pname))) {
printerr(0, "WARNING: Skipping keytab entry because "
- "we failed to unparse principal name: %s\n",
- error_message(code));
- krb5_kt_free_entry(context, &kte);
+ "we failed to unparse principal name: %s\n",
+ gssd_k5_err_msg(context, code));
+ k5_free_kt_entry(context, kte);
continue;
}
- printerr(2, "Processing keytab entry for principal '%s'\n",
+ printerr(4, "Processing keytab entry for principal '%s'\n",
pname);
- /* Just use the first keytab entry found for each realm */
- if ((!gssd_have_realm_ple((void *)&kte.principal->realm)) ) {
- printerr(2, "We WILL use this entry (%s)\n", pname);
- ple = malloc(sizeof(struct gssd_k5_kt_princ));
+ /* Use the first matching keytab entry found */
+ if ((realm_and_service_match(context, kte->principal, realm,
+ service))) {
+ printerr(4, "We WILL use this entry (%s)\n", pname);
+ ple = get_ple_by_princ(context, kte->principal);
+ /*
+ * Return, don't free, keytab entry if
+ * we were successful!
+ */
if (ple == NULL) {
- printerr(0, "ERROR: could not allocate storage "
- "for principal list entry\n");
-#ifdef HAVE_KRB5
- krb5_free_unparsed_name(context, pname);
-#else
- free(pname);
-#endif
- krb5_kt_free_entry(context, &kte);
retval = ENOMEM;
- goto out;
- }
- /* These will be filled in later */
- ple->next = NULL;
- ple->ccname = NULL;
- ple->endtime = 0;
- if ((ple->realm =
-#ifdef HAVE_KRB5
- strndup(kte.principal->realm.data,
- kte.principal->realm.length))
-#else
- strdup(kte.principal->realm))
-#endif
- == NULL) {
- printerr(0, "ERROR: %s while copying realm to "
- "principal list entry\n",
- "not enough memory");
-#ifdef HAVE_KRB5
- krb5_free_unparsed_name(context, pname);
-#else
- free(pname);
-#endif
- krb5_kt_free_entry(context, &kte);
- retval = ENOMEM;
- goto out;
- }
- if ((code = krb5_copy_principal(context,
- kte.principal, &ple->princ))) {
- printerr(0, "ERROR: %s while copying principal "
- "to principal list entry\n",
- error_message(code));
-#ifdef HAVE_KRB5
- krb5_free_unparsed_name(context, pname);
-#else
- free(pname);
-#endif
- krb5_kt_free_entry(context, &kte);
- retval = code;
- goto out;
- }
- if (gssd_k5_kt_princ_list == NULL)
- gssd_k5_kt_princ_list = ple;
- else {
- ple->next = gssd_k5_kt_princ_list;
- gssd_k5_kt_princ_list = ple;
+ k5_free_kt_entry(context, kte);
+ } else {
+ retval = 0;
+ *found = 1;
}
+ k5_free_unparsed_name(context, pname);
+ break;
}
else {
- printerr(2, "We will NOT use this entry (%s)\n",
+ printerr(4, "We will NOT use this entry (%s)\n",
pname);
}
-#ifdef HAVE_KRB5
- krb5_free_unparsed_name(context, pname);
-#else
- free(pname);
-#endif
- krb5_kt_free_entry(context, &kte);
+ k5_free_unparsed_name(context, pname);
+ k5_free_kt_entry(context, kte);
}
if ((code = krb5_kt_end_seq_get(context, kt, &cursor))) {
printerr(0, "WARNING: %s while ending keytab scan for "
"keytab '%s'\n",
- error_message(code), kt_name);
+ gssd_k5_err_msg(context, code), kt_name);
}
retval = 0;
}
/*
- * Depending on the version of Kerberos, we either need to use
- * a private function, or simply set the environment variable.
+ * Find a keytab entry to use for a given target hostname.
+ * Tries to find the most appropriate keytab to use given the
+ * name of the host we are trying to connect with.
*/
-static void
-gssd_set_krb5_ccache_name(char *ccname)
+static int
+find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname,
+ krb5_keytab_entry *kte)
{
-#ifdef USE_GSS_KRB5_CCACHE_NAME
- u_int maj_stat, min_stat;
+ krb5_error_code code;
+ const char *svcnames[] = { "root", "nfs", "host", NULL };
+ char **realmnames = NULL;
+ char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST];
+ int i, j, retval;
+ char *default_realm = NULL;
+ char *realm;
+ int tried_all = 0, tried_default = 0;
+ krb5_principal princ;
+
+
+ /* Get full target hostname */
+ retval = get_full_hostname(hostname, targethostname,
+ sizeof(targethostname));
+ if (retval)
+ goto out;
- printerr(2, "using gss_krb5_ccache_name to select krb5 ccache %s\n",
- ccname);
- maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL);
- if (maj_stat != GSS_S_COMPLETE) {
- printerr(0, "WARNING: gss_krb5_ccache_name with "
- "name '%s' failed (%s)\n",
- ccname, error_message(min_stat));
+ /* Get full local hostname */
+ retval = gethostname(myhostname, sizeof(myhostname));
+ if (retval) {
+ printerr(1, "%s while getting local hostname\n",
+ gssd_k5_err_msg(context, retval));
+ goto out;
}
-#else
+ retval = get_full_hostname(myhostname, myhostname, sizeof(myhostname));
+ if (retval)
+ goto out;
+
+ code = krb5_get_default_realm(context, &default_realm);
+ if (code) {
+ retval = code;
+ printerr(1, "%s while getting default realm name\n",
+ gssd_k5_err_msg(context, code));
+ goto out;
+ }
+
/*
- * Set the KRB5CCNAME environment variable to tell the krb5 code
- * which credentials cache to use. (Instead of using the private
- * function above for which there is no generic gssapi
- * equivalent.)
+ * Get the realm name(s) for the target hostname.
+ * In reality, this function currently only returns a
+ * single realm, but we code with the assumption that
+ * someday it may actually return a list.
*/
- printerr(2, "using environment variable to select krb5 ccache %s\n",
- ccname);
- setenv("KRB5CCNAME", ccname, 1);
-#endif
+ code = krb5_get_host_realm(context, targethostname, &realmnames);
+ if (code) {
+ printerr(0, "ERROR: %s while getting realm(s) for host '%s'\n",
+ gssd_k5_err_msg(context, code), targethostname);
+ retval = code;
+ goto out;
+ }
+
+ /*
+ * Try the "appropriate" realm first, and if nothing found for that
+ * realm, try the default realm (if it hasn't already been tried).
+ */
+ i = 0;
+ realm = realmnames[i];
+ while (1) {
+ if (realm == NULL) {
+ tried_all = 1;
+ if (!tried_default)
+ realm = default_realm;
+ }
+ if (tried_all && tried_default)
+ break;
+ if (strcmp(realm, default_realm) == 0)
+ tried_default = 1;
+ for (j = 0; svcnames[j] != NULL; j++) {
+ code = krb5_build_principal_ext(context, &princ,
+ strlen(realm),
+ realm,
+ strlen(svcnames[j]),
+ svcnames[j],
+ strlen(myhostname),
+ myhostname,
+ NULL);
+ if (code) {
+ printerr(1, "%s while building principal for "
+ "'%s/%s@%s'\n",
+ gssd_k5_err_msg(context, code),
+ svcnames[j], myhostname, realm);
+ continue;
+ }
+ code = krb5_kt_get_entry(context, kt, princ, 0, 0, kte);
+ krb5_free_principal(context, princ);
+ if (code) {
+ printerr(3, "%s while getting keytab entry for "
+ "'%s/%s@%s'\n",
+ gssd_k5_err_msg(context, code),
+ svcnames[j], myhostname, realm);
+ } else {
+ printerr(3, "Success getting keytab entry for "
+ "'%s/%s@%s'\n",
+ svcnames[j], myhostname, realm);
+ retval = 0;
+ goto out;
+ }
+ retval = code;
+ }
+ /*
+ * Nothing found with our hostname instance, now look for
+ * names with any instance (they must have an instance)
+ */
+ for (j = 0; svcnames[j] != NULL; j++) {
+ int found = 0;
+ code = gssd_search_krb5_keytab(context, kt, realm,
+ svcnames[j], &found, kte);
+ if (!code && found) {
+ printerr(3, "Success getting keytab entry for "
+ "%s/*@%s\n", svcnames[j], realm);
+ retval = 0;
+ goto out;
+ }
+ }
+ if (!tried_all) {
+ i++;
+ realm = realmnames[i];
+ }
+ }
+out:
+ if (default_realm)
+ k5_free_default_realm(context, default_realm);
+ if (realmnames)
+ krb5_free_host_realm(context, realmnames);
+ return retval;
}
/*==========================*/
gssd_set_krb5_ccache_name(ccname);
}
-/*
- * The first time through this routine, go through the keytab and
- * determine which keys we will try to use as machine credentials.
- * Every time through this routine, try to obtain credentials using
- * the keytab entries selected the first time through.
- *
- * Returns:
- * 0 => obtained one or more credentials
- * nonzero => error
- *
- */
-
-int
-gssd_refresh_krb5_machine_creds(void)
-{
- krb5_context context = NULL;
- krb5_keytab kt = NULL;;
- krb5_error_code code;
- int retval = -1;
- struct gssd_k5_kt_princ *ple;
- int gotone = 0;
- static int processed_keytab = 0;
-
-
- code = krb5_init_context(&context);
- if (code) {
- printerr(0, "ERROR: %s while initializing krb5 in "
- "gssd_refresh_krb5_machine_creds\n",
- error_message(code));
- retval = code;
- goto out;
- }
-
- printerr(1, "Using keytab file '%s'\n", keytabfile);
-
- if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
- printerr(0, "ERROR: %s while resolving keytab '%s'\n",
- error_message(code), keytabfile);
- goto out;
- }
-
- /* Only go through the keytab file once. Only print messages once. */
- if (gssd_k5_kt_princ_list == NULL && !processed_keytab) {
- processed_keytab = 1;
- gssd_process_krb5_keytab(context, kt, keytabfile);
- if (gssd_k5_kt_princ_list == NULL) {
- printerr(0, "ERROR: No usable keytab entries found in "
- "keytab '%s'\n", keytabfile);
- printerr(0, "Do you have a valid keytab entry for "
- "%s/<your.host>@<YOUR.REALM> in "
- "keytab file %s ?\n",
- GSSD_SERVICE_NAME, keytabfile);
- printerr(0, "Continuing without (machine) credentials "
- "- nfs4 mounts with Kerberos will fail\n");
- }
- }
-
- /*
- * If we don't have any keytab entries we liked, then we have a problem
- */
- if (gssd_k5_kt_princ_list == NULL) {
- retval = ENOENT;
- goto out;
- }
-
- /*
- * Now go through the list of saved entries and get initial
- * credentials for them (We can't do this while making the
- * list because it messes up the keytab iteration cursor
- * when we use the keytab to get credentials.)
- */
- for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
- if ((gssd_get_single_krb5_cred(context, kt, ple)) == 0) {
- gotone++;
- }
- }
- if (!gotone) {
- printerr(0, "ERROR: No usable machine credentials obtained\n");
- goto out;
- }
-
- retval = 0;
- out:
- if (kt) krb5_kt_close(context, kt);
- krb5_free_context(context);
-
- return retval;
-}
-
-
/*
* Return an array of pointers to names of credential cache files
* which can be used to try to create gss contexts with a server.
retval = -1;
*list = (char **) NULL;
- /* Refresh machine credentials */
- if ((retval = gssd_refresh_krb5_machine_creds())) {
- goto out;
- }
-
if ((l = (char **) malloc(listsize * sizeof(char *))) == NULL) {
retval = ENOMEM;
goto out;
}
+ /* Need to serialize list if we ever become multi-threaded! */
+
for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) {
if (ple->ccname) {
+ /* Make sure cred is up-to-date before returning it */
+ retval = gssd_refresh_krb5_machine_credential(NULL, ple);
+ if (retval)
+ continue;
if (i + 1 > listsize) {
listsize += listinc;
l = (char **)
code = krb5_init_context(&context);
if (code) {
printerr(0, "ERROR: %s while initializing krb5\n",
- error_message(code));
+ gssd_k5_err_msg(NULL, code));
goto out;
}
if ((code = krb5_cc_resolve(context, ple->ccname, &ccache))) {
printerr(0, "WARNING: %s while resolving credential "
"cache '%s' for destruction\n",
- error_message(code), ple->ccname);
+ gssd_k5_err_msg(context, code), ple->ccname);
continue;
}
if ((code = krb5_cc_destroy(context, ccache))) {
printerr(0, "WARNING: %s while destroying credential "
"cache '%s'\n",
- error_message(code), ple->ccname);
+ gssd_k5_err_msg(context, code), ple->ccname);
}
}
out:
krb5_free_context(context);
}
+/*
+ * Obtain (or refresh if necessary) Kerberos machine credentials
+ */
+int
+gssd_refresh_krb5_machine_credential(char *hostname,
+ struct gssd_k5_kt_princ *ple)
+{
+ krb5_error_code code = 0;
+ krb5_context context;
+ krb5_keytab kt = NULL;;
+ int retval = 0;
+
+ if (hostname == NULL && ple == NULL)
+ return EINVAL;
+
+ code = krb5_init_context(&context);
+ if (code) {
+ printerr(0, "ERROR: %s: %s while initializing krb5 context\n",
+ __FUNCTION__, gssd_k5_err_msg(NULL, code));
+ retval = code;
+ goto out;
+ }
+
+ if ((code = krb5_kt_resolve(context, keytabfile, &kt))) {
+ printerr(0, "ERROR: %s: %s while resolving keytab '%s'\n",
+ __FUNCTION__, gssd_k5_err_msg(context, code),
+ keytabfile);
+ goto out;
+ }
+
+ if (ple == NULL) {
+ krb5_keytab_entry kte;
+
+ code = find_keytab_entry(context, kt, hostname, &kte);
+ if (code) {
+ printerr(0, "ERROR: %s: no usable keytab entry found "
+ "in keytab %s for connection with host %s\n",
+ __FUNCTION__, keytabfile, hostname);
+ retval = code;
+ goto out;
+ }
+
+ ple = get_ple_by_princ(context, kte.principal);
+ k5_free_kt_entry(context, &kte);
+ if (ple == NULL) {
+ char *pname;
+ if ((krb5_unparse_name(context, kte.principal, &pname))) {
+ pname = NULL;
+ }
+ printerr(0, "ERROR: %s: Could not locate or create "
+ "ple struct for principal %s for connection "
+ "with host %s\n",
+ __FUNCTION__, pname ? pname : "<unparsable>",
+ hostname);
+ if (pname) k5_free_unparsed_name(context, pname);
+ goto out;
+ }
+ }
+ retval = gssd_get_single_krb5_cred(context, kt, ple);
+out:
+ if (kt)
+ krb5_kt_close(context, kt);
+ krb5_free_context(context);
+ return retval;
+}
+
+/*
+ * A common routine for getting the Kerberos error message
+ */
+const char *
+gssd_k5_err_msg(krb5_context context, krb5_error_code code)
+{
+ const char *msg = NULL;
+#if HAVE_KRB5_GET_ERROR_MESSAGE
+ if (context != NULL)
+ msg = krb5_get_error_message(context, code);
+#endif
+ if (msg != NULL)
+ return msg;
+#if HAVE_KRB5
+ return error_message(code);
+#else
+ if (context != NULL)
+ return krb5_get_err_text(context, code);
+ else
+ return error_message(code);
+#endif
+}
/*
* List of principals from our keytab that we
- * may try to get credentials for
+ * will try to use to obtain credentials
+ * (known as a principal list entry (ple))
*/
struct gssd_k5_kt_princ {
struct gssd_k5_kt_princ *next;
void gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername);
int gssd_get_krb5_machine_cred_list(char ***list);
-int gssd_refresh_krb5_machine_creds(void);
void gssd_free_krb5_machine_cred_list(char **list);
void gssd_setup_krb5_machine_gss_ccache(char *servername);
void gssd_destroy_krb5_machine_creds(void);
+int gssd_refresh_krb5_machine_credential(char *hostname,
+ struct gssd_k5_kt_princ *ple);
+const char *
+gssd_k5_err_msg(krb5_context context, krb5_error_code code);
#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
int limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid);
#endif
+/*
+ * Hide away some of the MIT vs. Heimdal differences
+ * here with macros...
+ */
+
+#ifdef HAVE_KRB5
+#define k5_free_unparsed_name(ctx, name) krb5_free_unparsed_name((ctx), (name))
+#define k5_free_default_realm(ctx, realm) krb5_free_default_realm((ctx), (realm))
+#define k5_free_kt_entry(ctx, kte) krb5_free_keytab_entry_contents((ctx),(kte))
+#else /* Heimdal */
+#define k5_free_unparsed_name(ctx, name) free(name)
+#define k5_free_default_realm(ctx, realm) free(realm)
+#define k5_free_kt_entry(ctx, kte) krb5_kt_free_entry((ctx),(kte))
+#undef USE_GSS_KRB5_CCACHE_NAME
+#define USE_GSS_KRB5_CCACHE_NAME 1
+#endif
+
#endif /* KRB5_UTIL_H */
dev_missing ++;
if (stat(path, &stb) != 0)
continue;
+ if (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode)) {
+ continue;
+ }
switch(fsidtype){
case FSID_DEV:
case FSID_MAJOR_MINOR:
}
if (found) {
- if (dump_to_cache(f, dom, path, &found->m_export) < 0)
+ if (dump_to_cache(f, dom, path, &found->m_export) < 0) {
+ xlog(L_WARNING,
+ "Cannot export %s, possibly unsupported filesystem"
+ " or fsid= required", path);
dump_to_cache(f, dom, path, NULL);
- else
+ } else
mountlist_add(dom, path);
} else {
dump_to_cache(f, dom, path, NULL);
return -1;
err = dump_to_cache(f, domain, exp->e_path, exp);
+ if (err) {
+ xlog(L_WARNING,
+ "Cannot export %s, possibly unsupported filesystem or"
+ " fsid= required", exp->e_path);
+ }
mountlist_add(domain, exp->e_path);
- while ((exp->e_flags & NFSEXP_CROSSMOUNT) && path) {
+ while (err == 0 && (exp->e_flags & NFSEXP_CROSSMOUNT) && path) {
/* really an 'if', but we can break out of
* a 'while' more easily */
/* Look along 'path' for other filesystems
dev = stb.st_dev;
while(path[l] == '/') {
char c;
- int err;
+ /* errors for submount should fail whole filesystem */
+ int err2;
l++;
while (path[l] != '/' && path[l])
l++;
c = path[l];
path[l] = 0;
- err = lstat(path, &stb);
+ err2 = lstat(path, &stb);
path[l] = c;
- if (err < 0)
+ if (err2 < 0)
break;
if (stb.st_dev == dev)
continue;
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
+#include "misc.h"
#include "statd.h"
#include "notlist.h"
+#include <arpa/inet.h>
/* Callback notify list. */
/* notify_list *cbnl = NULL; ... never used */
*my_name = argp->mon_id.my_id.my_name;
struct my_id *id = &argp->mon_id.my_id;
char *path;
+ char *cp;
int fd;
notify_list *clnt;
struct in_addr my_addr;
goto failure;
}
my_addr.s_addr = htonl(INADDR_LOOPBACK);
- my_name = "127.0.0.1";
/* 2. Reject any registrations for non-lockd services.
*
goto failure;
}
+ /* my_name must not have white space */
+ for (cp=my_name ; *cp ; cp++)
+ if (*cp == ' ' || *cp == '\t' || *cp == '\r' || *cp == '\n')
+ *cp = '_';
+
/*
* Hostnames checked OK.
* Now choose a hostname to use for matching. We cannot
mon_name, my_name);
/* But we'll let you pass anyway. */
- result.res_stat = STAT_SUCC;
- result.state = MY_STATE;
- return (&result);
+ goto success;
}
clnt = NL_NEXT(clnt);
}
goto failure;
}
{
- char buf[LINELEN + 1 + SM_MAXSTRLEN + 2];
+ char buf[LINELEN + 1 + SM_MAXSTRLEN*2 + 4];
char *e;
int i;
e = buf + sprintf(buf, "%08x %08x %08x %08x ",
for (i=0; i<SM_PRIV_SIZE; i++)
e += sprintf(e, "%02x", 0xff & (argp->priv[i]));
if (e+1-buf != LINELEN) abort();
- e += sprintf(e, " %s\n", mon_name);
+ e += sprintf(e, " %s %s\n", mon_name, my_name);
write(fd, buf, e-buf);
}
ha_callout("add-client", mon_name, my_name, -1);
nlist_insert(&rtnl, clnt);
close(fd);
-
+ dprintf(N_DEBUG, "MONITORING %s for %s", mon_name, my_name);
+ success:
result.res_stat = STAT_SUCC;
+ /* SUN's sm_inter.x says this should be "state number of local site".
+ * X/Open says '"state" will be contain the state of the remote NSM.'
+ * href=http://www.opengroup.org/onlinepubs/9629799/SM_MON.htm
+ * Linux lockd currently (2.6.21 and prior) ignores whatever is
+ * returned, and given the above contraction, it probably always will..
+ * So we just return what we always returned. If possible, we
+ * have already told lockd about our state number via a sysctl.
+ * If lockd wants the remote state, it will need to
+ * use SM_STAT (and prayer).
+ */
result.state = MY_STATE;
- dprintf(N_DEBUG, "MONITORING %s for %s", mon_name, my_name);
return (&result);
failure:
while (fgets(buf, sizeof(buf), f) != NULL) {
int addr, proc, prog, vers;
char priv[SM_PRIV_SIZE];
+ char *monname, *myname;
char *b;
int i;
notify_list *clnt;
b = strchr(buf, '\n');
if (b) *b = 0;
sscanf(buf, "%x %x %x %x ",
- &addr, &prog, &vers, &proc);
+ &addr, &prog, &vers, &proc, myname);
b = buf+36;
for (i=0; i<SM_PRIV_SIZE; i++) {
sscanf(b, "%2x", &p);
b += 2;
}
b++;
- clnt = nlist_new("127.0.0.1", b, 0);
+ monname = b;
+ while (*b && *b != ' ') b++;
+ if (*b) *b++ = '\0';
+ while (*b == ' ') b++;
+ myname = b;
+ clnt = nlist_new(myname, monname, 0);
if (!clnt)
break;
NL_ADDR(clnt).s_addr = addr;
char *mon_name = argp->mon_name,
*my_name = argp->my_id.my_name;
struct my_id *id = &argp->my_id;
+ char *cp;
#ifdef RESTRICTED_STATD
struct in_addr caller;
#endif
inet_ntoa(caller));
goto failure;
}
- my_name = "127.0.0.1";
#endif
+ /* my_name must not have white space */
+ for (cp=my_name ; *cp ; cp++)
+ if (*cp == ' ' || *cp == '\t' || *cp == '\r' || *cp == '\n')
+ *cp = '_';
+
/* Check if we're monitoring anyone. */
if (!(clnt = rtnl)) {
inet_ntoa(caller));
goto failure;
}
- my_name = "127.0.0.1";
#endif
result.state = MY_STATE;
statd_get_socket(void)
{
struct sockaddr_in sin;
+ struct servent *se;
+ int loopcnt = 100;
if (sockfd >= 0)
return sockfd;
- if ((sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
- note(N_CRIT, "Can't create socket: %m");
- return -1;
- }
+ while (loopcnt-- > 0) {
- FD_SET(sockfd, &SVC_FDSET);
+ if (sockfd >= 0) close(sockfd);
- memset(&sin, 0, sizeof(sin));
- sin.sin_family = AF_INET;
- sin.sin_addr.s_addr = INADDR_ANY;
+ if ((sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
+ note(N_CRIT, "Can't create socket: %m");
+ return -1;
+ }
- if (bindresvport(sockfd, &sin) < 0) {
- dprintf(N_WARNING,
- "process_hosts: can't bind to reserved port\n");
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = INADDR_ANY;
+
+ if (bindresvport(sockfd, &sin) < 0) {
+ dprintf(N_WARNING,
+ "process_hosts: can't bind to reserved port\n");
+ break;
+ }
+ se = getservbyport(sin.sin_port, "udp");
+ if (se == NULL)
+ break;
+ /* rather not use that port, try again */
}
+ FD_SET(sockfd, &SVC_FDSET);
return sockfd;
}
void nsm_log(int fac, const char *fmt, ...);
static int record_pid();
static void drop_privs(void);
+static void set_kernel_nsm_state(int state);
static struct nsm_host * hosts = NULL;
backup_hosts(_SM_DIR_PATH, _SM_BAK_PATH);
get_hosts(_SM_BAK_PATH);
+ /* Get and update the NSM state. This will call sync() */
+ nsm_state = nsm_get_state(opt_update_state);
+ set_kernel_nsm_state(nsm_state);
+
if (!opt_debug) {
if (!opt_quiet)
printf("Backgrounding to notify hosts...\n");
close(2);
}
- /* Get and update the NSM state. This will call sync() */
- nsm_state = nsm_get_state(opt_update_state);
-
notify();
if (hosts) {
nsm_address local_addr;
time_t failtime = 0;
int sock = -1;
+ int retry_cnt = 0;
+ retry:
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
perror("socket");
exit(1);
}
} else {
- (void) bindresvport(sock, (struct sockaddr_in *) &local_addr);
+ struct servent *se;
+ struct sockaddr_in *sin = (struct sockaddr_in *)&local_addr;
+ (void) bindresvport(sock, sin);
+ /* try to avoid known ports */
+ se = getservbyport(sin->sin_port, "udp");
+ if (se && retry_cnt < 100) {
+ retry_cnt++;
+ close(sock);
+ goto retry;
+ }
}
if (opt_max_retry)
exit(1);
}
}
+
+static void set_kernel_nsm_state(int state)
+{
+ int fd;
+
+ fd = open("/proc/sys/fs/nfs/nsm_local_state",O_WRONLY);
+ if (fd >= 0) {
+ char buf[20];
+ snprintf(buf, sizeof(buf), "%d", state);
+ write(fd, buf, strlen(buf));
+ close(fd);
+ }
+}
* status." My implementation is operative; it returns 'STAT_SUCC'
* whenever it can resolve the hostname that it's being asked to
* monitor, and returns 'STAT_FAIL' otherwise.
+ *
+ * sm_inter.x says the 'state' returned should be
+ * "state number of site sm_name". It is not clear how to get this.
+ * X/Open says:
+ * STAT_SUCC
+ * The NSM will monitor the given host. "sm_stat_res.state" contains
+ * the state of the NSM.
+ * Which implies that 'state' is the state number of the *local* NSM.
+ * href=http://www.opengroup.org/onlinepubs/9629799/SM_STAT.htm
+ *
+ * We return the *local* state as
+ * 1/ We have easy access to it.
+ * 2/ It might be useful to a remote client who needs it and has no
+ * other way to get it.
+ * 3/ That's what we always did in the past.
*/
struct sm_stat_res *
sm_stat_1_svc (struct sm_name *argp, struct svc_req *rqstp)
extern void sm_prog_1 (struct svc_req *, register SVCXPRT *);
extern int statd_get_socket(void);
+static void load_state_number(void);
#ifdef SIMULATIONS
extern void simulator (int, char **);
* pass on any SM_NOTIFY that arrives
*/
load_state();
-
+ load_state_number();
pmap_unset (SM_PROG, SM_VERS);
/* this registers both UDP and TCP services */
}
return 0;
}
+
+static void
+load_state_number(void)
+{
+ int fd;
+
+ if ((fd = open(SM_STAT_PATH, O_RDONLY)) == -1)
+ return;
+
+ read(fd, &MY_STATE, sizeof(MY_STATE));
+ close(fd);
+ fd = open("/proc/sys/fs/nfs/nsm_local_state",O_WRONLY);
+ if (fd >= 0) {
+ char buf[20];
+ snprintf(buf, sizeof(buf), "%d", MY_STATE);
+ write(fd, buf, strlen(buf));
+ close(fd);
+ }
+
+}