From: Ben Hutchings Date: Wed, 14 Jul 2010 01:50:49 +0000 (+0100) Subject: Merge branch 'upstream' X-Git-Tag: debian/1%1.1.0_rc2-1~1 X-Git-Url: https://git.decadent.org.uk/gitweb/?p=nfs-utils.git;a=commitdiff_plain;h=cdbdd5f613dc65748717e136243adf46d0c3fe31;hp=9ae0ab21cafe223226170d4c1a624f1c05685b02 Merge branch 'upstream' Conflicts: configure support/include/config.h.in --- diff --git a/NEWS b/NEWS index fe3571a..e71acf1 100644 --- a/NEWS +++ b/NEWS @@ -1,7 +1,7 @@ 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 @@ -18,7 +18,9 @@ Significant changes for nfs-utils 1.1.0 - March/April 2007 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). @@ -26,18 +28,33 @@ Significant changes for nfs-utils 1.1.0 - March/April 2007 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/@REALM + nfs/@REALM + host/@REALM + root/@REALM + nfs/@REALM + host/@REALM diff --git a/README b/README index 7f18118..aa4666f 100644 --- a/README +++ b/README @@ -45,6 +45,120 @@ simply 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 diff --git a/aclocal/kerberos5.m4 b/aclocal/kerberos5.m4 index b83e122..2475f50 100644 --- a/aclocal/kerberos5.m4 +++ b/aclocal/kerberos5.m4 @@ -93,6 +93,10 @@ AC_DEFUN([AC_KERBEROS_V5],[ 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!) diff --git a/configure b/configure index 0f561ff..85fb8c2 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /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 . # @@ -728,8 +728,8 @@ SHELL=${CONFIG_SHELL-/bin/sh} # 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 @@ -1450,7 +1450,7 @@ if test "$ac_init_help" = "long"; then # 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]... @@ -1520,7 +1520,7 @@ fi 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 @@ -1654,7 +1654,7 @@ fi 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, @@ -1668,7 +1668,7 @@ cat >config.log <<_ACEOF 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 $@ @@ -2442,7 +2442,7 @@ fi # Define the identity of the package. PACKAGE='nfs-utils' - VERSION='1.1.0-rc1' + VERSION='1.1.0-rc2' cat >>confdefs.h <<_ACEOF @@ -22484,12 +22484,12 @@ if test -n "$PKG_CONFIG"; then 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 @@ -22502,12 +22502,12 @@ if test -n "$PKG_CONFIG"; then 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 @@ -22526,14 +22526,14 @@ else _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 @@ -22544,7 +22544,7 @@ Alternatively, you may set the environment variables GSSAPI_CFLAGS 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 @@ -23544,6 +23544,78 @@ cat >>confdefs.h <<\_ACEOF #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 @@ -29993,7 +30065,7 @@ exec 6>&1 # 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 @@ -30046,7 +30118,7 @@ Report bugs to ." _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'`\\" diff --git a/configure.ac b/configure.ac index 65a2d02..3757208 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ 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) @@ -197,7 +197,7 @@ if test "$enable_nfsv4" = yes; then [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 diff --git a/support/include/config.h.in b/support/include/config.h.in index 19ff77c..209d958 100644 --- a/support/include/config.h.in +++ b/support/include/config.h.in @@ -120,6 +120,9 @@ /* 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 header file. */ #undef HAVE_LIBINTL_H diff --git a/support/nfs/svc_socket.c b/support/nfs/svc_socket.c index 6799d16..f44217a 100644 --- a/support/nfs/svc_socket.c +++ b/support/nfs/svc_socket.c @@ -101,8 +101,6 @@ svc_socket (u_long number, int type, int protocol, int reuse) } else { - if (bindresvport (sock, &addr)) - { addr.sin_port = 0; if (bind (sock, (struct sockaddr *) &addr, len) < 0) { @@ -110,7 +108,6 @@ svc_socket (u_long number, int type, int protocol, int reuse) (void) __close (sock); sock = -1; } - } } if (sock >= 0) diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c index 8c3f634..99618c9 100644 --- a/utils/exportfs/exportfs.c +++ b/utils/exportfs/exportfs.c @@ -12,6 +12,7 @@ #include #endif +#include #include #include #include @@ -32,7 +33,7 @@ static void exports_update(int verbose); 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) @@ -219,6 +220,7 @@ export_all(int verbose) exp->m_mayexport = 1; exp->m_changed = 1; exp->m_warned = 0; + validate_export(exp); } } } @@ -276,6 +278,7 @@ exportfs(char *arg, char *options, int verbose) exp->m_mayexport = 1; exp->m_changed = 1; exp->m_warned = 0; + validate_export(exp); if (hp) free (hp); } @@ -340,6 +343,87 @@ unexportfs(char *arg, int verbose) 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, ...) { diff --git a/utils/gssd/context_heimdal.c b/utils/gssd/context_heimdal.c index 5520cbc..6fb8fbd 100644 --- a/utils/gssd/context_heimdal.c +++ b/utils/gssd/context_heimdal.c @@ -72,14 +72,14 @@ int write_heimdal_enc_key(char **p, char *end, gss_ctx_id_t ctx) 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; } @@ -97,7 +97,7 @@ int write_heimdal_enc_key(char **p, char *end, gss_ctx_id_t ctx) 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; @@ -130,14 +130,14 @@ int write_heimdal_seq_key(char **p, char *end, gss_ctx_id_t ctx) 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; } diff --git a/utils/gssd/gssd.c b/utils/gssd/gssd.c index 747637c..b6c4ee4 100644 --- a/utils/gssd/gssd.c +++ b/utils/gssd/gssd.c @@ -165,10 +165,6 @@ main(int argc, char *argv[]) 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(); diff --git a/utils/gssd/gssd.man b/utils/gssd/gssd.man index f2ecd69..8da10b2 100644 --- a/utils/gssd/gssd.man +++ b/utils/gssd/gssd.man @@ -45,14 +45,25 @@ to use the keys found in .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/@ +.br + nfs/@ +.br + host/@ +.br + root/@ +.br + nfs/@ +.br + host/@ .TP .B -p path Tells diff --git a/utils/gssd/gssd_main_loop.c b/utils/gssd/gssd_main_loop.c index 0559f7b..84f04e9 100644 --- a/utils/gssd/gssd_main_loop.c +++ b/utils/gssd/gssd_main_loop.c @@ -116,6 +116,7 @@ gssd_run() init_client_list(); + printerr(1, "beginning poll\n"); while (1) { while (dir_changed) { dir_changed = 0; diff --git a/utils/gssd/gssd_proc.c b/utils/gssd/gssd_proc.c index 3b190f2..48880b6 100644 --- a/utils/gssd/gssd_proc.c +++ b/utils/gssd/gssd_proc.c @@ -555,7 +555,7 @@ int create_auth_rpc_client(struct clnt_info *clp, 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; } @@ -563,12 +563,12 @@ int create_auth_rpc_client(struct clnt_info *clp, /* 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); @@ -577,13 +577,13 @@ int create_auth_rpc_client(struct clnt_info *clp, 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; } @@ -617,7 +617,7 @@ int create_auth_rpc_client(struct clnt_info *clp, } 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; } @@ -700,14 +700,16 @@ handle_krb5_upcall(struct clnt_info *clp) 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++) { diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c index f1682b8..87bd7e4 100644 --- a/utils/gssd/krb5_util.c +++ b/utils/gssd/krb5_util.c @@ -103,8 +103,11 @@ #include #include +#include #include #include +#include +#include #include #include #include @@ -131,9 +134,7 @@ static int select_krb5_ccache(const struct dirent *d); 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 @@ -298,6 +299,7 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid) 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; @@ -329,6 +331,7 @@ gssd_get_single_krb5_cred(krb5_context context, int code; time_t now = time(0); char *cache_type; + char *pname = NULL; memset(&my_creds, 0, sizeof(my_creds)); @@ -345,6 +348,9 @@ gssd_get_single_krb5_cred(krb5_context context, 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); @@ -353,21 +359,12 @@ gssd_get_single_krb5_cred(krb5_context context, 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 : "", kt_name); -#ifdef HAVE_KRB5 - if (pname) krb5_free_unparsed_name(context, pname); -#else - if (pname) free(pname); -#endif goto out; } @@ -384,32 +381,38 @@ gssd_get_single_krb5_cred(krb5_context context, 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); @@ -417,155 +420,293 @@ gssd_get_single_krb5_cred(krb5_context context, } /* - * 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; @@ -574,34 +715,138 @@ gssd_process_krb5_keytab(krb5_context context, krb5_keytab kt, char *kt_name) } /* - * 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; } /*==========================*/ @@ -652,96 +897,6 @@ gssd_setup_krb5_machine_gss_ccache(char *ccname) 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/@ 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. @@ -764,18 +919,19 @@ gssd_get_krb5_machine_cred_list(char ***list) 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 **) @@ -831,7 +987,7 @@ gssd_destroy_krb5_machine_creds(void) 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; } @@ -841,17 +997,105 @@ gssd_destroy_krb5_machine_creds(void) 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 : "", + 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 +} diff --git a/utils/gssd/krb5_util.h b/utils/gssd/krb5_util.h index da04530..78ad45c 100644 --- a/utils/gssd/krb5_util.h +++ b/utils/gssd/krb5_util.h @@ -5,7 +5,8 @@ /* * 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; @@ -18,13 +19,33 @@ struct gssd_k5_kt_princ { 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 */ diff --git a/utils/mountd/cache.c b/utils/mountd/cache.c index 85e8975..d068843 100644 --- a/utils/mountd/cache.c +++ b/utils/mountd/cache.c @@ -397,6 +397,9 @@ void nfsd_fh(FILE *f) 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: @@ -620,9 +623,12 @@ void nfsd_export(FILE *f) } 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); @@ -697,9 +703,14 @@ int cache_export_ent(char *domain, struct exportent *exp, char *path) 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 @@ -717,16 +728,17 @@ int cache_export_ent(char *domain, struct exportent *exp, char *path) 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; diff --git a/utils/statd/callback.c b/utils/statd/callback.c index b19bb90..d5d036e 100644 --- a/utils/statd/callback.c +++ b/utils/statd/callback.c @@ -9,8 +9,10 @@ #ifdef HAVE_CONFIG_H #include #endif +#include "misc.h" #include "statd.h" #include "notlist.h" +#include /* Callback notify list. */ /* notify_list *cbnl = NULL; ... never used */ diff --git a/utils/statd/monitor.c b/utils/statd/monitor.c index b95b0ad..c447a26 100644 --- a/utils/statd/monitor.c +++ b/utils/statd/monitor.c @@ -40,6 +40,7 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) *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; @@ -70,7 +71,6 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) goto failure; } my_addr.s_addr = htonl(INADDR_LOOPBACK); - my_name = "127.0.0.1"; /* 2. Reject any registrations for non-lockd services. * @@ -127,6 +127,11 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) 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 @@ -166,9 +171,7 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) 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); } @@ -204,7 +207,7 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) 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 ", @@ -213,7 +216,7 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) for (i=0; ipriv[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); } @@ -222,10 +225,20 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) 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: @@ -258,6 +271,7 @@ void load_state(void) 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; @@ -266,7 +280,7 @@ void load_state(void) 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; imon_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 @@ -327,8 +347,12 @@ sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp) 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)) { @@ -394,7 +418,6 @@ sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp) inet_ntoa(caller)); goto failure; } - my_name = "127.0.0.1"; #endif result.state = MY_STATE; diff --git a/utils/statd/rmtcall.c b/utils/statd/rmtcall.c index 816a6f3..eb1919a 100644 --- a/utils/statd/rmtcall.c +++ b/utils/statd/rmtcall.c @@ -62,25 +62,37 @@ int 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; } diff --git a/utils/statd/sm-notify.c b/utils/statd/sm-notify.c index a94876d..98c03f9 100644 --- a/utils/statd/sm-notify.c +++ b/utils/statd/sm-notify.c @@ -88,6 +88,7 @@ static struct addrinfo *host_lookup(int, const char *); 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; @@ -166,6 +167,10 @@ usage: fprintf(stderr, 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"); @@ -184,9 +189,6 @@ usage: fprintf(stderr, close(2); } - /* Get and update the NSM state. This will call sync() */ - nsm_state = nsm_get_state(opt_update_state); - notify(); if (hosts) { @@ -213,7 +215,9 @@ notify(void) 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"); @@ -246,7 +250,16 @@ notify(void) 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) @@ -758,3 +771,16 @@ static void drop_privs(void) 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); + } +} diff --git a/utils/statd/stat.c b/utils/statd/stat.c index bcd3550..799239f 100644 --- a/utils/statd/stat.c +++ b/utils/statd/stat.c @@ -21,6 +21,21 @@ * 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) diff --git a/utils/statd/statd.c b/utils/statd/statd.c index 091ced9..8337b64 100644 --- a/utils/statd/statd.c +++ b/utils/statd/statd.c @@ -76,6 +76,7 @@ static struct option longopts[] = 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 **); @@ -483,7 +484,7 @@ int main (int argc, char **argv) * 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 */ @@ -526,3 +527,23 @@ int main (int argc, char **argv) } 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); + } + +}