From: Luk Claes Date: Tue, 22 May 2012 06:06:10 +0000 (+0200) Subject: Imported upstream 1.2.6 X-Git-Tag: upstream/1.2.6 X-Git-Url: https://git.decadent.org.uk/gitweb/?p=nfs-utils.git;a=commitdiff_plain;h=adee9b7bddedcddb8de4e0b7c2f778148b1f4d2b;hp=3ccd5e6e5c55c062c2bafb155fa54bad2cb1226a Imported upstream 1.2.6 --- diff --git a/.gitignore b/.gitignore index 7bd9921..96f9750 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ utils/rquotad/rquotad utils/rquotad/rquota.h utils/rquotad/rquota_xdr.c utils/showmount/showmount +utils/nfsdcld/nfsdcld utils/statd/statd tools/locktest/testlk tools/getiversion/getiversion diff --git a/README b/README index e7588cf..348f5d4 100644 --- a/README +++ b/README @@ -15,6 +15,8 @@ libraries. They are available from http://www.citi.umich.edu/projects/nfsv4/linux/libnfsidmap/ Otherwise use --disable-nfsv4 +To use the nfsdcld tracking daemon, nfsv4 support must be enabled, +and the libsqlite3 development libraries must be installed. 1. COMPILING @@ -80,7 +82,7 @@ scripts can be written to work correctly. 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). + security (Kerberos). C/ exportfs -av ; rpc.mountd It is important that exportfs be run before mountd so that @@ -106,12 +108,31 @@ scripts can be written to work correctly. the lock. rpc.statd is only needed for NFSv2 and NFSv3 support. - E/ rpc.nfsd + E/ nfsdcld + This daemon is only needed on kernels that support the nfsdcld + upcall, and only if the legacy client ID tracking isn't used. It + is also not needed if the server does not support NFSv4. + + To determine whether you need this or not, do the following: + + # cat /proc/fs/nfsd/versions + + That should yield a list of NFS versions that this kernel supports, + if "4" or later is not in that list, or they are prefixed with a "-" + then you don't need to run this daemon. Next: + + # cat /proc/fs/nfsd/nfsv4recoverydir + + If that file is not present, or the directory that the above command + outputs is not present, then this daemon is required in order to + support lock recovery by the clients when the server reboots. + + F/ 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 + G/ 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 @@ -130,7 +151,7 @@ scripts can be written to work correctly. 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). + securely (with Kerberos). C/ statd should be run before any NFSv2 or NFSv3 filesystem is mounted with remote locking (i.e. without -o nolock). diff --git a/aclocal/ipv6.m4 b/aclocal/ipv6.m4 index 5ee8fb6..75a8582 100644 --- a/aclocal/ipv6.m4 +++ b/aclocal/ipv6.m4 @@ -2,11 +2,6 @@ dnl Checks for IPv6 support dnl AC_DEFUN([AC_IPV6], [ - AC_CHECK_DECL([AI_ADDRCONFIG], - [AC_DEFINE([HAVE_DECL_AI_ADDRCONFIG], 1, - [Define this to 1 if AI_ADDRCONFIG macro is defined])], , - [ #include ]) - if test "$enable_ipv6" = yes; then dnl TI-RPC required for IPv6 @@ -15,15 +10,11 @@ AC_DEFUN([AC_IPV6], [ fi dnl IPv6-enabled networking functions required for IPv6 - AC_CHECK_FUNCS([getifaddrs getnameinfo bindresvport_sa], , + AC_CHECK_FUNCS([getifaddrs getnameinfo], , [AC_MSG_ERROR([Missing library functions needed for IPv6.])]) - dnl Need to detect presence of IPv6 networking at run time via - dnl getaddrinfo(3); old versions of glibc do not support ADDRCONFIG - AC_CHECK_DECL([AI_ADDRCONFIG], , - [AC_MSG_ERROR([full getaddrinfo(3) implementation needed for IPv6 support])], - [ #include ]) - + AC_CHECK_LIB([tirpc], [bindresvport_sa], [:], + [AC_MSG_ERROR([Missing library functions needed for IPv6.])]) fi ])dnl diff --git a/aclocal/kerberos5.m4 b/aclocal/kerberos5.m4 index dfa5738..7574e2d 100644 --- a/aclocal/kerberos5.m4 +++ b/aclocal/kerberos5.m4 @@ -31,7 +31,7 @@ AC_DEFUN([AC_KERBEROS_V5],[ fi if test "$K5CONFIG" != ""; then KRBCFLAGS=`$K5CONFIG --cflags` - KRBLIBS=`$K5CONFIG --libs gssapi` + KRBLIBS=`$K5CONFIG --libs` K5VERS=`$K5CONFIG --version | head -n 1 | awk '{split($(4),v,"."); if (v@<:@"3"@:>@ == "") v@<:@"3"@:>@ = "0"; print v@<:@"1"@:>@v@<:@"2"@:>@v@<:@"3"@:>@ }'` AC_DEFINE_UNQUOTED(KRB5_VERSION, $K5VERS, [Define this as the Kerberos version number]) if test -f $dir/include/gssapi/gssapi_krb5.h -a \ diff --git a/aclocal/libevent.m4 b/aclocal/libevent.m4 index 3c962b3..b5ac00f 100644 --- a/aclocal/libevent.m4 +++ b/aclocal/libevent.m4 @@ -2,8 +2,9 @@ dnl Checks for libevent AC_DEFUN([AC_LIBEVENT], [ dnl Check for libevent, but do not add -levent to LIBS - AC_CHECK_LIB([event], [event_dispatch], [libevent=1], + AC_CHECK_LIB([event], [event_dispatch], [LIBEVENT=-levent], [AC_MSG_ERROR([libevent not found.])]) + AC_SUBST(LIBEVENT) AC_CHECK_HEADERS([event.h], , [AC_MSG_ERROR([libevent headers not found.])]) diff --git a/aclocal/libnfsidmap.m4 b/aclocal/libnfsidmap.m4 index 484b1ec..ae697e8 100644 --- a/aclocal/libnfsidmap.m4 +++ b/aclocal/libnfsidmap.m4 @@ -3,7 +3,7 @@ dnl AC_DEFUN([AC_LIBNFSIDMAP], [ dnl Check for libnfsidmap, but do not add -lnfsidmap to LIBS - AC_CHECK_LIB([nfsidmap], [nfs4_init_name_mapping], [libnfsidmap=1], + AC_CHECK_LIB([nfsidmap], [nfs4_init_name_mapping], [LIBNFSIDMAP=-lnfsidmap], [AC_MSG_ERROR([libnfsidmap not found.])]) AC_CHECK_HEADERS([nfsidmap.h], , @@ -14,7 +14,10 @@ AC_DEFUN([AC_LIBNFSIDMAP], [ [AC_DEFINE([HAVE_NFS4_SET_DEBUG], 1, [Define to 1 if you have the `nfs4_set_debug' function.])]) - dnl only enable nfsidmap when libnfsidmap supports it - AC_CHECK_LIB([nfsidmap], [nfs4_owner_to_uid]) + dnl nfs4_owner_to_uid() doesn't appear in all versions of libnfsidmap + dnl We just need this test to set $ac_cv_lib_nfsidmap_nfs4_owner_to_uid + AC_CHECK_LIB([nfsidmap], [nfs4_owner_to_uid], [:]) + + AC_SUBST(LIBNFSIDMAP) ])dnl diff --git a/aclocal/libsqlite3.m4 b/aclocal/libsqlite3.m4 new file mode 100644 index 0000000..73d1e46 --- /dev/null +++ b/aclocal/libsqlite3.m4 @@ -0,0 +1,33 @@ +dnl Checks for matching sqlite3 header and library, and +dnl sufficient sqlite3 version. +dnl +AC_DEFUN([AC_SQLITE3_VERS], [ + AC_CHECK_HEADERS([sqlite3.h], ,) + + dnl look for the library; do not add to LIBS if found + AC_CHECK_LIB([sqlite3], [sqlite3_libversion_number], [LIBSQLITE=-lsqlite3], ,) + AC_SUBST(LIBSQLITE) + + AC_MSG_CHECKING(for suitable sqlite3 version) + + AC_CACHE_VAL([libsqlite3_cv_is_recent], + [ + saved_LIBS="$LIBS" + LIBS=-lsqlite3 + AC_TRY_RUN([ + #include + #include + int main() + { + int vers = sqlite3_libversion_number(); + + return vers != SQLITE_VERSION_NUMBER || + vers < 3003000; + } + ], [libsqlite3_cv_is_recent=yes], [libsqlite3_cv_is_recent=no], + [libsqlite3_cv_is_recent=unknown]) + LIBS="$saved_LIBS"]) + + AC_MSG_RESULT($libsqlite3_cv_is_recent) + AM_CONDITIONAL(CONFIG_SQLITE3, [test "$libsqlite3_cv_is_recent" = "yes"]) +])dnl diff --git a/aclocal/libtirpc.m4 b/aclocal/libtirpc.m4 index 9f0fde0..19b8361 100644 --- a/aclocal/libtirpc.m4 +++ b/aclocal/libtirpc.m4 @@ -13,8 +13,8 @@ AC_DEFUN([AC_LIBTIRPC], [ if test "$enable_tirpc" != "no"; then - dnl look for the library; add to LIBS if found - AC_CHECK_LIB([tirpc], [clnt_tli_create], , + dnl look for the library + AC_CHECK_LIB([tirpc], [clnt_tli_create], [:], [if test "$enable_tirpc" = "yes"; then AC_MSG_ERROR([libtirpc not found.]) else @@ -37,4 +37,15 @@ AC_DEFUN([AC_LIBTIRPC], [ fi + dnl now set $LIBTIRPC accordingly + if test "$enable_tirpc" != "no"; then + AC_DEFINE([HAVE_LIBTIRPC], 1, + [Define to 1 if you have and wish to use libtirpc.]) + LIBTIRPC="-ltirpc" + else + LIBTIRPC="" + fi + + AC_SUBST(LIBTIRPC) + ])dnl diff --git a/configure.ac b/configure.ac index 80fb39d..583c603 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.2.5],[linux-nfs@vger.kernel.org],[nfs-utils]) +AC_INIT([linux nfs-utils],[1.2.6],[linux-nfs@vger.kernel.org],[nfs-utils]) AC_CANONICAL_BUILD([]) AC_CANONICAL_HOST([]) AC_CONFIG_MACRO_DIR(aclocal) @@ -24,9 +24,8 @@ AC_ARG_WITH(statedir, statedir=/var/lib/nfs) AC_SUBST(statedir) AC_ARG_WITH(statdpath, - [AC_HELP_STRING([--with-statdpath=/foo @<:@default=/var/lib/nfs@:>@], - [define statd's state dir as /foo instead of the NFS statedir] - )], + [AC_HELP_STRING([--with-statdpath=/foo], + [define the statd state dir as /foo instead of the NFS statedir @<:@default=/var/lib/nfs@:>@])], statdpath=$withval, statdpath=$statedir ) @@ -186,6 +185,12 @@ else AM_CONDITIONAL(MOUNT_CONFIG, [test "$enable_mount" = "yes"]) fi +AC_ARG_ENABLE(nfsdcld, + [AC_HELP_STRING([--enable-nfsdcld], + [Create nfsdcld NFSv4 clientid tracking daemon. @<:@default=no@:>@])], + enable_nfsdcld=$enableval, + enable_nfsdcld="no") + dnl Check for TI-RPC library and headers AC_LIBTIRPC @@ -249,6 +254,8 @@ AC_CHECK_FUNC([getservbyname], , AC_CHECK_LIB([crypt], [crypt], [LIBCRYPT="-lcrypt"]) +AC_CHECK_LIB([dl], [dlclose], [LIBDL="-ldl"]) + if test "$enable_nfsv4" = yes; then dnl check for libevent libraries and headers AC_LIBEVENT @@ -259,6 +266,20 @@ if test "$enable_nfsv4" = yes; then dnl check for the keyutils libraries and headers AC_KEYUTILS + dnl Check for sqlite3 + AC_SQLITE3_VERS + + if test "$enable_nfsdcld" = "yes"; then + AC_CHECK_HEADERS([libgen.h sys/inotify.h], , + AC_MSG_ERROR([Cannot find header needed for nfsdcld])) + + if test "$libsqlite3_cv_is_recent" != "yes" ; then + AC_MSG_ERROR([nfsdcld requires sqlite3]) + fi + fi + + AM_CONDITIONAL(CONFIG_NFSDCLD, [test "$enable_nfsdcld" = "yes" ]) + dnl librpcsecgss already has a dependency on libgssapi, dnl but we need to make sure we get the right version if test "$enable_gss" = yes; then @@ -269,6 +290,15 @@ dnl enable nfsidmap when its support by libnfsidmap AM_CONDITIONAL(CONFIG_NFSIDMAP, [test "$ac_cv_header_keyutils_h$ac_cv_lib_nfsidmap_nfs4_owner_to_uid" = "yesyes"]) +if test "$enable_nfsv41" = yes; then + AC_CHECK_LIB([devmapper], [dm_task_create], [LIBDEVMAPPER="-ldevmapper"], AC_MSG_ERROR([libdevmapper needed])) + AC_CHECK_HEADER(libdevmapper.h, , AC_MSG_ERROR([Cannot find devmapper header file libdevmapper.h])) +fi + +dnl enable nfsidmap when its support by libnfsidmap +AM_CONDITIONAL(CONFIG_NFSIDMAP, [test "$ac_cv_header_keyutils_h$ac_cv_lib_nfsidmap_nfs4_owner_to_uid" = "yesyes"]) + + if test "$knfsd_cv_glibc2" = no; then AC_CHECK_LIB(bsd, daemon, [LIBBSD="-lbsd"]) fi @@ -293,6 +323,7 @@ AC_SUBST(LIBSOCKET) AC_SUBST(LIBCRYPT) AC_SUBST(LIBBSD) AC_SUBST(LIBBLKID) +AC_SUBST(LIBDL) if test "$enable_libmount" != no; then AC_CHECK_LIB(mount, mnt_context_do_mount, [LIBMOUNT="-lmount"], AC_MSG_ERROR([libmount needed])) @@ -308,9 +339,6 @@ if test "$enable_gss" = yes; then dnl 'gss' also depends on nfsidmap.h - at least for svcgssd_proc.c AC_LIBNFSIDMAP - AC_CHECK_HEADERS([spkm3.h], , - [AC_MSG_WARN([Could not locate SPKM3 header; will not have SPKM3 support])]) - dnl Check for Kerberos V5 AC_KERBEROS_V5 @@ -330,7 +358,7 @@ AC_CHECK_HEADERS([arpa/inet.h fcntl.h libintl.h limits.h \ stdlib.h string.h sys/file.h sys/ioctl.h sys/mount.h \ sys/param.h sys/socket.h sys/time.h sys/vfs.h \ syslog.h unistd.h com_err.h et/com_err.h \ - ifaddrs.h]) + ifaddrs.h nfs-plugin.h]) dnl ************************************************************* dnl Checks for typedefs, structures, and compiler characteristics @@ -452,6 +480,7 @@ AC_CONFIG_FILES([ tools/nfs-iostat/Makefile utils/Makefile utils/blkmapd/Makefile + utils/nfsdcld/Makefile utils/exportfs/Makefile utils/gssd/Makefile utils/idmapd/Makefile @@ -462,6 +491,7 @@ AC_CONFIG_FILES([ utils/nfsidmap/Makefile utils/showmount/Makefile utils/statd/Makefile + utils/osd_login/Makefile tests/Makefile tests/nsm_client/Makefile]) AC_OUTPUT diff --git a/support/include/cld.h b/support/include/cld.h new file mode 100644 index 0000000..f14a9ab --- /dev/null +++ b/support/include/cld.h @@ -0,0 +1,56 @@ +/* + * Upcall description for nfsdcld communication + * + * Copyright (c) 2012 Red Hat, Inc. + * Author(s): Jeff Layton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _NFSD_CLD_H +#define _NFSD_CLD_H + +/* latest upcall version available */ +#define CLD_UPCALL_VERSION 1 + +/* defined by RFC3530 */ +#define NFS4_OPAQUE_LIMIT 1024 + +enum cld_command { + Cld_Create, /* create a record for this cm_id */ + Cld_Remove, /* remove record of this cm_id */ + Cld_Check, /* is this cm_id allowed? */ + Cld_GraceDone, /* grace period is complete */ +}; + +/* representation of long-form NFSv4 client ID */ +struct cld_name { + uint16_t cn_len; /* length of cm_id */ + unsigned char cn_id[NFS4_OPAQUE_LIMIT]; /* client-provided */ +} __attribute__((packed)); + +/* message struct for communication with userspace */ +struct cld_msg { + uint8_t cm_vers; /* upcall version */ + uint8_t cm_cmd; /* upcall command */ + int16_t cm_status; /* return code */ + uint32_t cm_xid; /* transaction id */ + union { + int64_t cm_gracetime; /* grace period start time */ + struct cld_name cm_name; + } __attribute__((packed)) cm_u; +} __attribute__((packed)); + +#endif /* !_NFSD_CLD_H */ diff --git a/support/include/exportfs.h b/support/include/exportfs.h index 01e87dd..99916e5 100644 --- a/support/include/exportfs.h +++ b/support/include/exportfs.h @@ -32,6 +32,10 @@ enum { FSLOC_STUB }; +#ifndef EXP_LOCKFILE +#define EXP_LOCKFILE "/var/lib/nfs/export-lock" +#endif + typedef struct mclient { struct mclient * m_next; char * m_hostname; diff --git a/support/include/nfs/debug.h b/support/include/nfs/debug.h index d391e91..dbec5ba 100644 --- a/support/include/nfs/debug.h +++ b/support/include/nfs/debug.h @@ -76,6 +76,9 @@ enum { #define NFSDBG_CALLBACK 0x0100 #define NFSDBG_CLIENT 0x0200 #define NFSDBG_MOUNT 0x0400 +#define NFSDBG_FSCACHE 0x0800 +#define NFSDBG_PNFS 0x1000 +#define NFSDBG_PNFS_LD 0x2000 #define NFSDBG_ALL 0xFFFF #endif /* _NFS_DEBUG_H */ diff --git a/support/include/pseudoflavors.h b/support/include/pseudoflavors.h index c21087b..deb052b 100644 --- a/support/include/pseudoflavors.h +++ b/support/include/pseudoflavors.h @@ -4,9 +4,6 @@ #define RPC_AUTH_GSS_LKEY 390006 #define RPC_AUTH_GSS_LKEYI 390007 #define RPC_AUTH_GSS_LKEYP 390008 -#define RPC_AUTH_GSS_SPKM 390009 -#define RPC_AUTH_GSS_SPKMI 390010 -#define RPC_AUTH_GSS_SPKMP 390011 struct flav_info { char *flavour; diff --git a/support/nfs/conffile.c b/support/nfs/conffile.c index fa0dc6b..5015e94 100644 --- a/support/nfs/conffile.c +++ b/support/nfs/conffile.c @@ -49,6 +49,8 @@ #include "conffile.h" #include "xlog.h" +#pragma GCC visibility push(hidden) + static void conf_load_defaults(void); static int conf_set(int , char *, char *, char *, char *, int , int ); @@ -211,7 +213,7 @@ static void conf_parse_line(int trans, char *line, size_t sz) { char *val, *ptr; - size_t i; + size_t i, valsize; size_t j; static char *section = 0; static char *arg = 0; @@ -256,13 +258,14 @@ conf_parse_line(int trans, char *line, size_t sz) val++, j++; if (*val) i = j; - section = malloc(i); + section = malloc(i+1); if (!section) { xlog_warn("conf_parse_line: %d: malloc (%lu) failed", ln, (unsigned long)i); return; } strncpy(section, line, i); + section[i] = '\0'; if (arg) free(arg); @@ -297,23 +300,16 @@ conf_parse_line(int trans, char *line, size_t sz) } line[strcspn (line, " \t=")] = '\0'; val = line + i + 1 + strspn (line + i + 1, " \t"); + valsize = 0; + while (val[valsize++]); - /* Skip trailing comments, if any */ - for (j = 0; j < sz - (val - line); j++) { - if (val[j] == '#' || val[j] == ';') { + /* Skip trailing spaces and comments */ + for (j = 0; j < valsize; j++) { + if (val[j] == '#' || val[j] == ';' || isspace(val[j])) { val[j] = '\0'; break; } } - - /* Skip trailing whitespace, if any */ - for (j--; j > 0; j--) { - if (isspace(val[j])) - val[j] = '\0'; - else - break; - } - /* XXX Perhaps should we not ignore errors? */ conf_set(trans, section, arg, line, val, 0, 0); return; diff --git a/support/nfs/exports.c b/support/nfs/exports.c index c96500f..84a2b08 100644 --- a/support/nfs/exports.c +++ b/support/nfs/exports.c @@ -39,12 +39,6 @@ struct flav_info flav_map[] = { { "krb5", RPC_AUTH_GSS_KRB5 }, { "krb5i", RPC_AUTH_GSS_KRB5I }, { "krb5p", RPC_AUTH_GSS_KRB5P }, - { "lipkey", RPC_AUTH_GSS_LKEY }, - { "lipkey-i", RPC_AUTH_GSS_LKEYI }, - { "lipkey-p", RPC_AUTH_GSS_LKEYP }, - { "spkm3", RPC_AUTH_GSS_SPKM }, - { "spkm3i", RPC_AUTH_GSS_SPKMI }, - { "spkm3p", RPC_AUTH_GSS_SPKMP }, { "unix", AUTH_UNIX }, { "sys", AUTH_SYS }, { "null", AUTH_NULL }, diff --git a/support/nfs/nfsctl.c b/support/nfs/nfsctl.c index 89fa1a4..fec775f 100644 --- a/support/nfs/nfsctl.c +++ b/support/nfs/nfsctl.c @@ -11,16 +11,22 @@ #endif #include +#include #include #include "nfslib.h" /* compatibility hack... */ -#ifndef __NR_nfsctl +#if !defined(__NR_nfsctl) && defined(__NR_nfsservctl) #define __NR_nfsctl __NR_nfsservctl #endif int nfsctl (int cmd, struct nfsctl_arg * argp, union nfsctl_res * resp) { +#ifdef __NR_nfsctl return syscall (__NR_nfsctl, cmd, argp, resp); +#else + errno = ENOSYS; + return -1; +#endif } diff --git a/tools/rpcdebug/rpcdebug.c b/tools/rpcdebug/rpcdebug.c index 275a491..444616d 100644 --- a/tools/rpcdebug/rpcdebug.c +++ b/tools/rpcdebug/rpcdebug.c @@ -167,6 +167,9 @@ static struct flagmap { FLAG(NFS, CALLBACK), FLAG(NFS, CLIENT), FLAG(NFS, MOUNT), + FLAG(NFS, FSCACHE), + FLAG(NFS, PNFS), + FLAG(NFS, PNFS_LD), FLAG(NFS, ALL), /* nfsd */ diff --git a/tools/rpcgen/Makefile.am b/tools/rpcgen/Makefile.am index 51a2bfa..8a9ec89 100644 --- a/tools/rpcgen/Makefile.am +++ b/tools/rpcgen/Makefile.am @@ -12,6 +12,7 @@ rpcgen_SOURCES = rpc_clntout.c rpc_cout.c rpc_hout.c rpc_main.c \ rpcgen_CFLAGS=$(CFLAGS_FOR_BUILD) rpcgen_CPPLAGS=$(CPPFLAGS_FOR_BUILD) rpcgen_LDFLAGS=$(LDFLAGS_FOR_BUILD) +rpcgen_LDADD=$(LIBTIRPC) MAINTAINERCLEANFILES = Makefile.in diff --git a/utils/Makefile.am b/utils/Makefile.am index d074b85..09045dd 100644 --- a/utils/Makefile.am +++ b/utils/Makefile.am @@ -21,6 +21,10 @@ if CONFIG_MOUNT OPTDIRS += mount endif +if CONFIG_NFSDCLD +OPTDIRS += nfsdcld +endif + SUBDIRS = \ exportfs \ mountd \ @@ -28,6 +32,7 @@ SUBDIRS = \ nfsstat \ showmount \ statd \ + osd_login \ $(OPTDIRS) MAINTAINERCLEANFILES = Makefile.in diff --git a/utils/blkmapd/device-process.c b/utils/blkmapd/device-process.c index 27ff374..652a7a8 100644 --- a/utils/blkmapd/device-process.c +++ b/utils/blkmapd/device-process.c @@ -296,7 +296,7 @@ decode_blk_volume(uint32_t **pp, uint32_t *end, struct bl_volume *vols, int voln off_t stripe_unit = vol->param.bv_stripe_unit; /* Check limitations imposed by device-mapper */ if ((stripe_unit & (stripe_unit - 1)) != 0 - || stripe_unit < (off_t) (PAGE_SIZE >> 9)) + || stripe_unit < (off_t) (sysconf(_SC_PAGE_SIZE) >> 9)) return -EIO; BLK_READBUF(p, end, 4); READ32(vol->bv_vol_n); diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c index 7432a65..a3323d7 100644 --- a/utils/exportfs/exportfs.c +++ b/utils/exportfs/exportfs.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,41 @@ static void usage(const char *progname); static void validate_export(nfs_export *exp); static int matchhostname(const char *hostname1, const char *hostname2); static void export_d_read(const char *dname); +static void grab_lockfile(void); +static void release_lockfile(void); + +static const char *lockfile = EXP_LOCKFILE; +static int _lockfd = -1; + +/* + * If we aren't careful, changes made by exportfs can be lost + * when multiple exports process run at once: + * + * exportfs process 1 exportfs process 2 + * ------------------------------------------ + * reads etab version A reads etab version A + * adds new export B adds new export C + * writes A+B writes A+C + * + * The locking in support/export/xtab.c will prevent mountd from + * seeing a partially written version of etab, and will prevent + * the two writers above from writing simultaneously and + * corrupting etab, but to prevent problems like the above we + * need these additional lockfile() routines. + */ +static void +grab_lockfile() +{ + _lockfd = open(lockfile, O_CREAT|O_RDWR, 0666); + if (_lockfd != -1) + lockf(_lockfd, F_LOCK, 0); +} +static void +release_lockfile() +{ + if (_lockfd != -1) + lockf(_lockfd, F_ULOCK, 0); +} int main(int argc, char **argv) @@ -129,6 +165,13 @@ main(int argc, char **argv) return 0; } } + + /* + * Serialize things as best we can + */ + grab_lockfile(); + atexit(release_lockfile); + if (f_export && ! f_ignore) { export_read(_PATH_EXPORTS); export_d_read(_PATH_EXPORTS_D); diff --git a/utils/exportfs/exportfs.man b/utils/exportfs/exportfs.man index 364f247..8853486 100644 --- a/utils/exportfs/exportfs.man +++ b/utils/exportfs/exportfs.man @@ -177,7 +177,7 @@ In this way .B exportfs can be used to modify the export options of an already exported directory. .SS Unexporting Directories -The third synopsis shows how to unexported a currently exported directory. +The third synopsis shows how to unexport a currently exported directory. When using .BR "exportfs -ua" , all entries listed in diff --git a/utils/exportfs/exports.man b/utils/exportfs/exports.man index 54adfeb..bc1de73 100644 --- a/utils/exportfs/exports.man +++ b/utils/exportfs/exports.man @@ -293,24 +293,6 @@ be explicitly requested with either of the synonymous .IR auth_nlm , or .IR secure_locks . -.TP -.IR no_acl -On some specially patched kernels, and when exporting filesystems that -support ACLs, this option tells -.B nfsd -not to reveal ACLs to clients, so -they will see only a subset of actual permissions on the given file -system. This option is safe for filesystems used by NFSv2 clients and -old NFSv3 clients that perform access decisions locally. Current -NFSv3 clients use the ACCESS RPC to perform all access decisions on -the server. Note that the -.I no_acl -option only has effect on kernels specially patched to support it, and -when exporting filesystems with ACL support. The default is to export -with ACL support (i.e. by default, -.I no_acl -is off). - .\".TP .\".I noaccess .\"This makes everything below the directory inaccessible for the named diff --git a/utils/exportfs/nfsd.man b/utils/exportfs/nfsd.man index 7365a1b..47b73be 100644 --- a/utils/exportfs/nfsd.man +++ b/utils/exportfs/nfsd.man @@ -12,7 +12,7 @@ nfsd \- special filesystem for controlling Linux NFS server .SH DESCRIPTION The .B nfsd -filesytem is a special filesystem which provides access to the Linux +filesystem is a special filesystem which provides access to the Linux NFS server. The filesystem consists of a single directory which contains a number of files. These files are actually gateways into the NFS server. Writing to them can affect the server. Reading from @@ -86,7 +86,7 @@ should be followed by a newline, with white-space separating the fields, and octal quoting of special characters. On writing this, the program will be able to read back a filehandle -for that path as exported to the given client. The filehandles length +for that path as exported to the given client. The filehandle's length will be at most the number of bytes given. The filehandle will be represented in hex with a leading '\ex'. @@ -165,7 +165,7 @@ file. The user-space program might then write .ti +5 nfsd 127.0.0.1 1057206953 localhost .br -to indicate that 127.0.0.1 should map to localhost, atleast for now. +to indicate that 127.0.0.1 should map to localhost, at least for now. If the program uses select(2) or poll(2) to discover if it can read from the diff --git a/utils/gssd/Makefile.am b/utils/gssd/Makefile.am index d7888ad..2365704 100644 --- a/utils/gssd/Makefile.am +++ b/utils/gssd/Makefile.am @@ -17,7 +17,6 @@ COMMON_SRCS = \ context_mit.c \ context_heimdal.c \ context_lucid.c \ - context_spkm3.c \ gss_util.c \ gss_oids.c \ err_util.c \ @@ -40,7 +39,7 @@ gssd_SOURCES = \ gssd_LDADD = ../../support/nfs/libnfs.a \ $(RPCSECGSS_LIBS) $(GSSGLUE_LIBS) $(KRBLIBS) -gssd_LDFLAGS = $(KRBLDFLAGS) +gssd_LDFLAGS = $(KRBLDFLAGS) $(LIBTIRPC) gssd_CFLAGS = $(AM_CFLAGS) $(CFLAGS) \ $(RPCSECGSS_CFLAGS) $(GSSGLUE_CFLAGS) $(KRBCFLAGS) @@ -58,8 +57,8 @@ svcgssd_SOURCES = \ svcgssd_LDADD = \ ../../support/nfs/libnfs.a \ - $(RPCSECGSS_LIBS) $(GSSGLUE_LIBS) -lnfsidmap \ - $(KRBLIBS) + $(RPCSECGSS_LIBS) $(GSSGLUE_LIBS) $(LIBNFSIDMAP) \ + $(KRBLIBS) $(LIBTIRPC) svcgssd_LDFLAGS = $(KRBLDFLAGS) diff --git a/utils/gssd/context.c b/utils/gssd/context.c index 1e50bbf..fee7da2 100644 --- a/utils/gssd/context.c +++ b/utils/gssd/context.c @@ -51,10 +51,6 @@ serialize_context_for_kernel(gss_ctx_id_t ctx, { if (g_OID_equal(&krb5oid, mech)) return serialize_krb5_ctx(ctx, buf, endtime); -#ifdef HAVE_SPKM3_H - else if (g_OID_equal(&spkm3oid, mech)) - return serialize_spkm3_ctx(ctx, buf, endtime); -#endif else { printerr(0, "ERROR: attempting to serialize context with " "unknown/unsupported mechanism oid\n"); diff --git a/utils/gssd/context.h b/utils/gssd/context.h index c9cb0bd..0e437f4 100644 --- a/utils/gssd/context.h +++ b/utils/gssd/context.h @@ -43,8 +43,6 @@ int serialize_context_for_kernel(gss_ctx_id_t ctx, gss_buffer_desc *buf, gss_OID mech, int32_t *endtime); -int serialize_spkm3_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf, - int32_t *endtime); int serialize_krb5_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf, int32_t *endtime); diff --git a/utils/gssd/context_lucid.c b/utils/gssd/context_lucid.c index 3e695ab..64146d7 100644 --- a/utils/gssd/context_lucid.c +++ b/utils/gssd/context_lucid.c @@ -80,6 +80,7 @@ prepare_krb5_rfc1964_buffer(gss_krb5_lucid_context_v1_t *lctx, uint32_t i; char *skd, *dkd; gss_buffer_desc fakeoid; + int err; /* * The new Kerberos interface to get the gss context @@ -138,11 +139,10 @@ prepare_krb5_rfc1964_buffer(gss_krb5_lucid_context_v1_t *lctx, dkd = (char *) enc_key.data; for (i = 0; i < enc_key.length; i++) dkd[i] = skd[i] ^ 0xf0; - if (write_lucid_keyblock(&p, end, &enc_key)) { - free(enc_key.data); - goto out_err; - } + err = write_lucid_keyblock(&p, end, &enc_key); free(enc_key.data); + if (err) + goto out_err; if (write_lucid_keyblock(&p, end, &lctx->rfc1964_kd.ctx_key)) goto out_err; @@ -153,7 +153,6 @@ out_err: printerr(0, "ERROR: failed serializing krb5 context for kernel\n"); if (buf->value) free(buf->value); buf->length = 0; - if (enc_key.data) free(enc_key.data); return -1; } diff --git a/utils/gssd/context_spkm3.c b/utils/gssd/context_spkm3.c deleted file mode 100644 index b927475..0000000 --- a/utils/gssd/context_spkm3.c +++ /dev/null @@ -1,184 +0,0 @@ -/* - Copyright (c) 2004 The Regents of the University of Michigan. - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - 3. Neither the name of the University nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#ifdef HAVE_CONFIG_H -#include -#endif /* HAVE_CONFIG_H */ - -#include -#include -#include -#include -#include -#include -#include "gss_util.h" -#include "gss_oids.h" -#include "err_util.h" -#include "context.h" - -#ifdef HAVE_SPKM3_H - -#include - -/* - * Function: prepare_spkm3_ctx_buffer() - * - * Prepare spkm3 lucid context for the kernel - * - * buf->length should be: - * - * version 4 - * ctx_id 4 + 12 - * qop 4 - * mech_used 4 + 7 - * ret_fl 4 - * req_fl 4 - * share 4 + key_len - * conf_alg 4 + oid_len - * d_conf_key 4 + key_len - * intg_alg 4 + oid_len - * d_intg_key 4 + key_len - * kyestb 4 + oid_len - * owl alg 4 + oid_len -*/ -static int -prepare_spkm3_ctx_buffer(gss_spkm3_lucid_ctx_t *lctx, gss_buffer_desc *buf) -{ - char *p, *end; - unsigned int buf_size = 0; - - buf_size = sizeof(lctx->version) + - lctx->ctx_id.length + sizeof(lctx->ctx_id.length) + - sizeof(lctx->endtime) + - sizeof(lctx->mech_used.length) + lctx->mech_used.length + - sizeof(lctx->ret_flags) + - sizeof(lctx->conf_alg.length) + lctx->conf_alg.length + - sizeof(lctx->derived_conf_key.length) + - lctx->derived_conf_key.length + - sizeof(lctx->intg_alg.length) + lctx->intg_alg.length + - sizeof(lctx->derived_integ_key.length) + - lctx->derived_integ_key.length; - - if (!(buf->value = calloc(1, buf_size))) - goto out_err; - p = buf->value; - end = buf->value + buf_size; - - if (WRITE_BYTES(&p, end, lctx->version)) - goto out_err; - printerr(2, "DEBUG: exporting version = %d\n", lctx->version); - - if (write_buffer(&p, end, &lctx->ctx_id)) - goto out_err; - printerr(2, "DEBUG: exporting ctx_id(%d)\n", lctx->ctx_id.length); - - if (WRITE_BYTES(&p, end, lctx->endtime)) - goto out_err; - printerr(2, "DEBUG: exporting endtime = %d\n", lctx->endtime); - - if (write_buffer(&p, end, &lctx->mech_used)) - goto out_err; - printerr(2, "DEBUG: exporting mech oid (%d)\n", lctx->mech_used.length); - - if (WRITE_BYTES(&p, end, lctx->ret_flags)) - goto out_err; - printerr(2, "DEBUG: exporting ret_flags = %d\n", lctx->ret_flags); - - if (write_buffer(&p, end, &lctx->conf_alg)) - goto out_err; - printerr(2, "DEBUG: exporting conf_alg oid (%d)\n", lctx->conf_alg.length); - - if (write_buffer(&p, end, &lctx->derived_conf_key)) - goto out_err; - printerr(2, "DEBUG: exporting conf key (%d)\n", lctx->derived_conf_key.length); - - if (write_buffer(&p, end, &lctx->intg_alg)) - goto out_err; - printerr(2, "DEBUG: exporting intg_alg oid (%d)\n", lctx->intg_alg.length); - - if (write_buffer(&p, end, &lctx->derived_integ_key)) - goto out_err; - printerr(2, "DEBUG: exporting intg key (%d)\n", lctx->derived_integ_key.length); - - buf->length = p - (char *)buf->value; - return 0; -out_err: - printerr(0, "ERROR: failed serializing spkm3 context for kernel\n"); - if (buf->value) free(buf->value); - buf->length = 0; - - return -1; -} - -/* ANDROS: need to determine which fields of the spkm3_gss_ctx_id_desc_t - * are needed in the kernel for get_mic, validate, wrap, unwrap, and destroy - * and only export those fields to the kernel. - */ -int -serialize_spkm3_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf, int32_t *endtime) -{ - OM_uint32 vers, ret, maj_stat, min_stat; - void *ret_ctx = 0; - gss_spkm3_lucid_ctx_t *lctx; - - printerr(1, "serialize_spkm3_ctx called\n"); - - printerr(2, "DEBUG: serialize_spkm3_ctx: lucid version!\n"); - maj_stat = gss_export_lucid_sec_context(&min_stat, &ctx, 1, &ret_ctx); - if (maj_stat != GSS_S_COMPLETE) - goto out_err; - - lctx = (gss_spkm3_lucid_ctx_t *)ret_ctx; - - vers = lctx->version; - if (vers != 1) { - printerr(0, "ERROR: unsupported spkm3 context version %d\n", - vers); - goto out_err; - } - ret = prepare_spkm3_ctx_buffer(lctx, buf); - - if (endtime) - *endtime = lctx->endtime; - - maj_stat = gss_free_lucid_sec_context(&min_stat, ctx, ret_ctx); - - if (maj_stat != GSS_S_COMPLETE) - printerr(0, "WARN: failed to free lucid sec context\n"); - if (ret) - goto out_err; - printerr(2, "DEBUG: serialize_spkm3_ctx: success\n"); - return 0; - -out_err: - printerr(2, "DEBUG: serialize_spkm3_ctx: failed\n"); - return -1; -} -#endif /* HAVE_SPKM3_H */ diff --git a/utils/gssd/gss_oids.c b/utils/gssd/gss_oids.c index a59c4a6..4362de2 100644 --- a/utils/gssd/gss_oids.c +++ b/utils/gssd/gss_oids.c @@ -38,6 +38,3 @@ /* from kerberos source, gssapi_krb5.c */ gss_OID_desc krb5oid = {9, "\052\206\110\206\367\022\001\002\002"}; - -gss_OID_desc spkm3oid = - {7, "\053\006\001\005\005\001\003"}; diff --git a/utils/gssd/gss_oids.h b/utils/gssd/gss_oids.h index 8b0a352..fde8532 100644 --- a/utils/gssd/gss_oids.h +++ b/utils/gssd/gss_oids.h @@ -34,7 +34,6 @@ #include extern gss_OID_desc krb5oid; -extern gss_OID_desc spkm3oid; #ifndef g_OID_equal #define g_OID_equal(o1,o2) \ diff --git a/utils/gssd/gssd.c b/utils/gssd/gssd.c index ccadb07..7825255 100644 --- a/utils/gssd/gssd.c +++ b/utils/gssd/gssd.c @@ -57,7 +57,7 @@ char pipefs_dir[PATH_MAX] = GSSD_PIPEFS_DIR; char keytabfile[PATH_MAX] = GSSD_DEFAULT_KEYTAB_FILE; -char ccachedir[PATH_MAX] = GSSD_DEFAULT_CRED_DIR; +char ccachedir[PATH_MAX] = GSSD_DEFAULT_CRED_DIR ":" GSSD_USER_CRED_DIR; char *ccachesearch[GSSD_MAX_CCACHE_SEARCH + 1]; int use_memcache = 0; int root_uses_machine_creds = 1; @@ -85,7 +85,7 @@ sig_hup(int signal) static void usage(char *progname) { - fprintf(stderr, "usage: %s [-f] [-M] [-n] [-v] [-r] [-p pipefsdir] [-k keytab] [-d ccachedir] [-t timeout] [-R preferred realm]\n", + fprintf(stderr, "usage: %s [-f] [-l] [-M] [-n] [-v] [-r] [-p pipefsdir] [-k keytab] [-d ccachedir] [-t timeout] [-R preferred realm]\n", progname); exit(1); } @@ -102,7 +102,7 @@ main(int argc, char *argv[]) char *progname; memset(ccachesearch, 0, sizeof(ccachesearch)); - while ((opt = getopt(argc, argv, "fvrmnMp:k:d:t:R:")) != -1) { + while ((opt = getopt(argc, argv, "fvrlmnMp:k:d:t:R")) != -1) { switch (opt) { case 'f': fg = 1; @@ -143,6 +143,13 @@ main(int argc, char *argv[]) case 'R': preferred_realm = strdup(optarg); break; + case 'l': +#ifdef HAVE_SET_ALLOWABLE_ENCTYPES + limit_to_legacy_enctypes = 1; +#else + errx(1, "Setting encryption type not support by Kerberos libraries."); +#endif + break; default: usage(argv[0]); break; diff --git a/utils/gssd/gssd.h b/utils/gssd/gssd.h index b1b5793..28a8206 100644 --- a/utils/gssd/gssd.h +++ b/utils/gssd/gssd.h @@ -45,6 +45,7 @@ #define DNOTIFY_SIGNAL (SIGRTMIN + 3) #define GSSD_DEFAULT_CRED_DIR "/tmp" +#define GSSD_USER_CRED_DIR "/run/user" #define GSSD_DEFAULT_CRED_PREFIX "krb5cc_" #define GSSD_DEFAULT_MACHINE_CRED_SUFFIX "machine" #define GSSD_DEFAULT_KEYTAB_FILE "/etc/krb5.keytab" @@ -55,7 +56,7 @@ /* * The gss mechanisms that we can handle */ -enum {AUTHTYPE_KRB5, AUTHTYPE_SPKM3, AUTHTYPE_LIPKEY}; +enum {AUTHTYPE_KRB5, AUTHTYPE_LIPKEY}; @@ -80,8 +81,6 @@ struct clnt_info { char *protocol; int krb5_fd; int krb5_poll_index; - int spkm3_fd; - int spkm3_poll_index; int gssd_fd; int gssd_poll_index; struct sockaddr_storage addr; @@ -98,7 +97,6 @@ struct topdirs_info { void init_client_list(void); int update_client_list(void); void handle_krb5_upcall(struct clnt_info *clp); -void handle_spkm3_upcall(struct clnt_info *clp); void handle_gssd_upcall(struct clnt_info *clp); void gssd_run(void); diff --git a/utils/gssd/gssd.man b/utils/gssd/gssd.man index 073379d..d8138fa 100644 --- a/utils/gssd/gssd.man +++ b/utils/gssd/gssd.man @@ -6,7 +6,7 @@ .SH NAME rpc.gssd \- rpcsec_gss daemon .SH SYNOPSIS -.B "rpc.gssd [-f] [-n] [-k keytab] [-p pipefsdir] [-v] [-r] [-d ccachedir]" +.B "rpc.gssd [-f] [-n] [-k keytab] [-l] [-p pipefsdir] [-v] [-r] [-d ccachedir]" .SH DESCRIPTION The rpcsec_gss protocol gives a means of using the gss-api generic security api to provide security for protocols using rpc (in particular, nfs). Before @@ -70,6 +70,30 @@ for "machine credentials" is now: If this search order does not use the correct key then provide a keytab file that contains only correct keys. .TP +.B -l +Tells +.B rpc.gssd +to limit session keys to Single DES even if the kernel supports stronger +encryption types. Service ticket encryption is still governed by what +the KDC believes the target server supports. This way the client can +access a server that has strong keys in its keytab for ticket decryption +but whose kernel only supports Single DES. +.IP +The alternative is to put only Single DES keys in the server's keytab +and limit encryption types for its principal to Single DES on the KDC +which will cause service tickets for this server to be encrypted using +only Single DES and (as a side-effect) contain only Single DES session +keys. +.IP +This legacy behaviour is only required for older servers +(pre nfs-utils-1.2.4). If the server has a recent kernel, Kerberos +implementation and nfs-utils it will work just fine with stronger +encryption. +.IP +.B Note: +This option is only available with Kerberos libraries that +support setable encryption types. +.TP .B -p path Tells .B rpc.gssd diff --git a/utils/gssd/gssd_main_loop.c b/utils/gssd/gssd_main_loop.c index b06c223..cec09ea 100644 --- a/utils/gssd/gssd_main_loop.c +++ b/utils/gssd/gssd_main_loop.c @@ -98,17 +98,6 @@ scan_poll_results(int ret) if (!ret) break; } - i = clp->spkm3_poll_index; - if (i >= 0 && pollarray[i].revents) { - if (pollarray[i].revents & POLLHUP) - dir_changed = 1; - if (pollarray[i].revents & POLLIN) - handle_spkm3_upcall(clp); - pollarray[clp->spkm3_poll_index].revents = 0; - ret--; - if (!ret) - break; - } } }; diff --git a/utils/gssd/gssd_proc.c b/utils/gssd/gssd_proc.c index 41328c9..aa39435 100644 --- a/utils/gssd/gssd_proc.c +++ b/utils/gssd/gssd_proc.c @@ -299,15 +299,11 @@ destroy_client(struct clnt_info *clp) if (clp->krb5_poll_index != -1) memset(&pollarray[clp->krb5_poll_index], 0, sizeof(struct pollfd)); - if (clp->spkm3_poll_index != -1) - memset(&pollarray[clp->spkm3_poll_index], 0, - sizeof(struct pollfd)); if (clp->gssd_poll_index != -1) memset(&pollarray[clp->gssd_poll_index], 0, sizeof(struct pollfd)); if (clp->dir_fd != -1) close(clp->dir_fd); if (clp->krb5_fd != -1) close(clp->krb5_fd); - if (clp->spkm3_fd != -1) close(clp->spkm3_fd); if (clp->gssd_fd != -1) close(clp->gssd_fd); free(clp->dirname); free(clp->servicename); @@ -327,10 +323,8 @@ insert_new_clnt(void) goto out; } clp->krb5_poll_index = -1; - clp->spkm3_poll_index = -1; clp->gssd_poll_index = -1; clp->krb5_fd = -1; - clp->spkm3_fd = -1; clp->gssd_fd = -1; clp->dir_fd = -1; @@ -355,30 +349,22 @@ process_clnt_dir_files(struct clnt_info * clp) snprintf(name, sizeof(name), "%s/krb5", clp->dirname); clp->krb5_fd = open(name, O_RDWR); } - if (clp->spkm3_fd == -1) { - snprintf(name, sizeof(name), "%s/spkm3", clp->dirname); - clp->spkm3_fd = open(name, O_RDWR); - } /* If we opened a gss-specific pipe, let's try opening * the new upcall pipe again. If we succeed, close * gss-specific pipe(s). */ - if (clp->krb5_fd != -1 || clp->spkm3_fd != -1) { + if (clp->krb5_fd != -1) { clp->gssd_fd = open(gname, O_RDWR); if (clp->gssd_fd != -1) { if (clp->krb5_fd != -1) close(clp->krb5_fd); clp->krb5_fd = -1; - if (clp->spkm3_fd != -1) - close(clp->spkm3_fd); - clp->spkm3_fd = -1; } } } - if ((clp->krb5_fd == -1) && (clp->spkm3_fd == -1) && - (clp->gssd_fd == -1)) + if ((clp->krb5_fd == -1) && (clp->gssd_fd == -1)) return -1; snprintf(info_file_name, sizeof(info_file_name), "%s/info", clp->dirname); @@ -431,15 +417,6 @@ insert_clnt_poll(struct clnt_info *clp) pollarray[clp->krb5_poll_index].events |= POLLIN; } - if ((clp->spkm3_fd != -1) && (clp->spkm3_poll_index == -1)) { - if (get_poll_index(&clp->spkm3_poll_index)) { - printerr(0, "ERROR: Too many spkm3 clients\n"); - return -1; - } - pollarray[clp->spkm3_poll_index].fd = clp->spkm3_fd; - pollarray[clp->spkm3_poll_index].events |= POLLIN; - } - return 0; } @@ -839,13 +816,6 @@ int create_auth_rpc_client(struct clnt_info *clp, sec.mech = (gss_OID)&krb5oid; sec.req_flags = GSS_C_MUTUAL_FLAG; } - else if (authtype == AUTHTYPE_SPKM3) { - sec.mech = (gss_OID)&spkm3oid; - /* XXX sec.req_flags = GSS_C_ANON_FLAG; - * Need a way to switch.... - */ - sec.req_flags = GSS_C_MUTUAL_FLAG; - } else { printerr(0, "ERROR: Invalid authentication type (%d) " "in create_auth_rpc_client\n", authtype); @@ -919,9 +889,8 @@ int create_auth_rpc_client(struct clnt_info *clp, auth = authgss_create_default(rpc_clnt, clp->servicename, &sec); if (!auth) { /* Our caller should print appropriate message */ - printerr(2, "WARNING: Failed to create %s context for " + printerr(2, "WARNING: Failed to create krb5 context for " "user with uid %d for server %s\n", - (authtype == AUTHTYPE_KRB5 ? "krb5":"spkm3"), uid, clp->servername); goto out_fail; } @@ -949,6 +918,23 @@ int create_auth_rpc_client(struct clnt_info *clp, goto out; } +static char * +user_cachedir(char *dirname, uid_t uid) +{ + struct passwd *pw; + char *ptr; + + if ((pw = getpwuid(uid)) == NULL) { + printerr(0, "user_cachedir: Failed to find '%d' uid" + " for cache directory\n"); + return NULL; + } + ptr = malloc(strlen(dirname)+strlen(pw->pw_name)+2); + if (ptr) + sprintf(ptr, "%s/%s", dirname, pw->pw_name); + + return ptr; +} /* * this code uses the userland rpcsec gss library to create a krb5 * context on behalf of the kernel @@ -963,7 +949,7 @@ process_krb5_upcall(struct clnt_info *clp, uid_t uid, int fd, char *tgtname, gss_buffer_desc token; char **credlist = NULL; char **ccname; - char **dirname; + char **dirname, *dir, *userdir; int create_resp = -1; int err, downcall_err = -EACCES; @@ -1006,7 +992,22 @@ process_krb5_upcall(struct clnt_info *clp, uid_t uid, int fd, char *tgtname, service == NULL)) { /* Tell krb5 gss which credentials cache to use */ for (dirname = ccachesearch; *dirname != NULL; dirname++) { - err = gssd_setup_krb5_user_gss_ccache(uid, clp->servername, *dirname); + /* See if the user name is needed */ + if (strncmp(*dirname, GSSD_USER_CRED_DIR, + strlen(GSSD_USER_CRED_DIR)) == 0) { + userdir = user_cachedir(*dirname, uid); + if (userdir == NULL) + continue; + dir = userdir; + } else + dir = *dirname; + + err = gssd_setup_krb5_user_gss_ccache(uid, clp->servername, dir); + + if (userdir) { + free(userdir); + userdir = NULL; + } if (err == -EKEYEXPIRED) downcall_err = -EKEYEXPIRED; else if (!err) @@ -1103,59 +1104,6 @@ out_return_error: goto out; } -/* - * this code uses the userland rpcsec gss library to create an spkm3 - * context on behalf of the kernel - */ -static void -process_spkm3_upcall(struct clnt_info *clp, uid_t uid, int fd) -{ - CLIENT *rpc_clnt = NULL; - AUTH *auth = NULL; - struct authgss_private_data pd; - gss_buffer_desc token; - - printerr(2, "handling spkm3 upcall (%s)\n", clp->dirname); - - token.length = 0; - token.value = NULL; - - if (create_auth_rpc_client(clp, &rpc_clnt, &auth, uid, AUTHTYPE_SPKM3)) { - printerr(0, "WARNING: Failed to create spkm3 context for " - "user with uid %d\n", uid); - goto out_return_error; - } - - if (!authgss_get_private_data(auth, &pd)) { - printerr(0, "WARNING: Failed to obtain authentication " - "data for user with uid %d for server %s\n", - uid, clp->servername); - goto out_return_error; - } - - if (serialize_context_for_kernel(pd.pd_ctx, &token, &spkm3oid, NULL)) { - printerr(0, "WARNING: Failed to serialize spkm3 context for " - "user with uid %d for server\n", - uid, clp->servername); - goto out_return_error; - } - - do_downcall(fd, uid, &pd, &token); - -out: - if (token.value) - free(token.value); - if (auth) - AUTH_DESTROY(auth); - if (rpc_clnt) - clnt_destroy(rpc_clnt); - return; - -out_return_error: - do_error_downcall(fd, uid, -1); - goto out; -} - void handle_krb5_upcall(struct clnt_info *clp) { @@ -1170,20 +1118,6 @@ handle_krb5_upcall(struct clnt_info *clp) return process_krb5_upcall(clp, uid, clp->krb5_fd, NULL, NULL); } -void -handle_spkm3_upcall(struct clnt_info *clp) -{ - uid_t uid; - - if (read(clp->spkm3_fd, &uid, sizeof(uid)) < (ssize_t)sizeof(uid)) { - printerr(0, "WARNING: failed reading uid from spkm3 " - "upcall pipe: %s\n", strerror(errno)); - return; - } - - return process_spkm3_upcall(clp, uid, clp->spkm3_fd); -} - void handle_gssd_upcall(struct clnt_info *clp) { @@ -1292,8 +1226,6 @@ handle_gssd_upcall(struct clnt_info *clp) if (strcmp(mech, "krb5") == 0) process_krb5_upcall(clp, uid, clp->gssd_fd, target, service); - else if (strcmp(mech, "spkm3") == 0) - process_spkm3_upcall(clp, uid, clp->gssd_fd); else printerr(0, "WARNING: handle_gssd_upcall: " "received unknown gss mech '%s'\n", mech); diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c index 4b13fa1..887d118 100644 --- a/utils/gssd/krb5_util.c +++ b/utils/gssd/krb5_util.c @@ -129,6 +129,10 @@ /* Global list of principals/cache file names for machine credentials */ struct gssd_k5_kt_princ *gssd_k5_kt_princ_list = NULL; +#ifdef HAVE_SET_ALLOWABLE_ENCTYPES +int limit_to_legacy_enctypes = 0; +#endif + /*==========================*/ /*=== Internal routines ===*/ /*==========================*/ @@ -1342,7 +1346,7 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec) * If we failed for any reason to produce global * list of supported enctypes, use local default here. */ - if (krb5_enctypes == NULL) + if (krb5_enctypes == NULL || limit_to_legacy_enctypes) maj_stat = gss_set_allowable_enctypes(&min_stat, credh, &krb5oid, num_enctypes, enctypes); else diff --git a/utils/gssd/krb5_util.h b/utils/gssd/krb5_util.h index b42b91e..cd6e107 100644 --- a/utils/gssd/krb5_util.h +++ b/utils/gssd/krb5_util.h @@ -36,6 +36,7 @@ char *gssd_k5_err_msg(krb5_context context, krb5_error_code code); void gssd_k5_get_default_realm(char **def_realm); #ifdef HAVE_SET_ALLOWABLE_ENCTYPES +extern int limit_to_legacy_enctypes; int limit_krb5_enctypes(struct rpc_gss_sec *sec); #endif diff --git a/utils/gssd/svcgssd_mech2file.c b/utils/gssd/svcgssd_mech2file.c index 65de8d0..ecd908b 100644 --- a/utils/gssd/svcgssd_mech2file.c +++ b/utils/gssd/svcgssd_mech2file.c @@ -53,8 +53,6 @@ struct mech2file { struct mech2file m2f[] = { {{9, "\052\206\110\206\367\022\001\002\002"}, "krb5"}, - {{7, "\053\006\001\005\005\001\003"}, "spkm3"}, - {{7, "\053\006\001\005\005\001\009"}, "lipkey"}, {{0,0},""}, }; diff --git a/utils/gssd/svcgssd_proc.c b/utils/gssd/svcgssd_proc.c index c714d99..0d4f78d 100644 --- a/utils/gssd/svcgssd_proc.c +++ b/utils/gssd/svcgssd_proc.c @@ -369,12 +369,8 @@ get_hostbased_client_name(gss_name_t client_name, gss_OID mech, if (g_OID_equal(&krb5oid, mech)) { if (get_krb5_hostbased_name(&name, &cname) == 0) *hostbased_name = cname; - } - - /* No support for SPKM3, just print a warning (for now) */ - if (g_OID_equal(&spkm3oid, mech)) { - printerr(1, "WARNING: get_hostbased_client_name: " - "no hostbased_name support for SPKM3\n"); + } else { + printerr(1, "WARNING: unknown/unsupport mech OID\n"); } res = 0; diff --git a/utils/idmapd/Makefile.am b/utils/idmapd/Makefile.am index 4328e41..58b33ec 100644 --- a/utils/idmapd/Makefile.am +++ b/utils/idmapd/Makefile.am @@ -16,7 +16,7 @@ idmapd_SOURCES = \ nfs_idmap.h \ queue.h -idmapd_LDADD = -levent -lnfsidmap ../../support/nfs/libnfs.a +idmapd_LDADD = $(LIBEVENT) $(LIBNFSIDMAP) ../../support/nfs/libnfs.a MAINTAINERCLEANFILES = Makefile.in diff --git a/utils/idmapd/idmapd.c b/utils/idmapd/idmapd.c index 19d9114..e80efb4 100644 --- a/utils/idmapd/idmapd.c +++ b/utils/idmapd/idmapd.c @@ -778,8 +778,8 @@ nfsopen(struct idmap_client *ic) } else { event_set(&ic->ic_event, ic->ic_fd, EV_READ, nfscb, ic); event_add(&ic->ic_event, NULL); - fcntl(ic->ic_dirfd, F_SETSIG, 0); fcntl(ic->ic_dirfd, F_NOTIFY, 0); + fcntl(ic->ic_dirfd, F_SETSIG, 0); if (verbose > 0) xlog_warn("Opened %s", ic->ic_path); } diff --git a/utils/mount/Makefile.am b/utils/mount/Makefile.am index 7bc3e2b..7627854 100644 --- a/utils/mount/Makefile.am +++ b/utils/mount/Makefile.am @@ -24,7 +24,8 @@ EXTRA_DIST += nfsmount.conf endif mount_nfs_LDADD = ../../support/nfs/libnfs.a \ - ../../support/export/libexport.a + ../../support/export/libexport.a \ + $(LIBTIRPC) mount_nfs_SOURCES = $(mount_common) diff --git a/utils/mount/mount_libmount.c b/utils/mount/mount_libmount.c index e450d79..e8f17a9 100644 --- a/utils/mount/mount_libmount.c +++ b/utils/mount/mount_libmount.c @@ -346,6 +346,21 @@ static int mount_main(struct libmnt_context *cxt, int argc, char **argv) if (chk_mountpoint(mount_point)) goto err; + + /* + * The libmount strictly uses only options from fstab if running in + * restricted mode (suid, non-root user). This is done in + * mnt_context_prepare_mount() by default. + * + * We have to read fstab before nfsmount.conf, otherwise the options + * from nfsmount.conf will be ignored (overwrited). + */ + rc = mnt_context_apply_fstab(cxt); + if (rc) { + nfs_error(_("%s: failed to apply fstab options\n"), progname); + goto err; + } + /* * Concatenate mount options from the configuration file */ diff --git a/utils/mount/nfs.man b/utils/mount/nfs.man index ce40933..87e27e1 100644 --- a/utils/mount/nfs.man +++ b/utils/mount/nfs.man @@ -372,14 +372,8 @@ Valid security flavors are .BR sys , .BR krb5 , .BR krb5i , -.BR krb5p , -.BR lkey , -.BR lkeyi , -.BR lkeyp , -.BR spkm , -.BR spkmi , and -.BR spkmp . +.BR krb5p , Refer to the SECURITY CONSIDERATIONS section for details. .TP 1.5i .BR sharecache " / " nosharecache @@ -506,6 +500,8 @@ Specifying a netid that uses TCP forces all traffic from the command and the NFS client to use TCP. Specifying a netid that uses UDP forces all traffic types to use UDP. .IP +.B Before using NFS over UDP, refer to the TRANSPORT METHODS section. +.IP If the .B proto mount option is not specified, the @@ -520,6 +516,8 @@ The option is an alternative to specifying .BR proto=udp. It is included for compatibility with other operating systems. +.IP +.B Before using NFS over UDP, refer to the TRANSPORT METHODS section. .TP 1.5i .B tcp The @@ -1076,6 +1074,83 @@ or options are specified more than once on the same mount command line, then the value of the rightmost instance of each of these options takes effect. +.SS "Using NFS over UDP on high-speed links" +Using NFS over UDP on high-speed links such as Gigabit +.BR "can cause silent data corruption" . +.P +The problem can be triggered at high loads, and is caused by problems in +IP fragment reassembly. NFS read and writes typically transmit UDP packets +of 4 Kilobytes or more, which have to be broken up into several fragments +in order to be sent over the Ethernet link, which limits packets to 1500 +bytes by default. This process happens at the IP network layer and is +called fragmentation. +.P +In order to identify fragments that belong together, IP assigns a 16bit +.I IP ID +value to each packet; fragments generated from the same UDP packet +will have the same IP ID. The receiving system will collect these +fragments and combine them to form the original UDP packet. This process +is called reassembly. The default timeout for packet reassembly is +30 seconds; if the network stack does not receive all fragments of +a given packet within this interval, it assumes the missing fragment(s) +got lost and discards those it already received. +.P +The problem this creates over high-speed links is that it is possible +to send more than 65536 packets within 30 seconds. In fact, with +heavy NFS traffic one can observe that the IP IDs repeat after about +5 seconds. +.P +This has serious effects on reassembly: if one fragment gets lost, +another fragment +.I from a different packet +but with the +.I same IP ID +will arrive within the 30 second timeout, and the network stack will +combine these fragments to form a new packet. Most of the time, network +layers above IP will detect this mismatched reassembly - in the case +of UDP, the UDP checksum, which is a 16 bit checksum over the entire +packet payload, will usually not match, and UDP will discard the +bad packet. +.P +However, the UDP checksum is 16 bit only, so there is a chance of 1 in +65536 that it will match even if the packet payload is completely +random (which very often isn't the case). If that is the case, +silent data corruption will occur. +.P +This potential should be taken seriously, at least on Gigabit +Ethernet. +Network speeds of 100Mbit/s should be considered less +problematic, because with most traffic patterns IP ID wrap around +will take much longer than 30 seconds. +.P +It is therefore strongly recommended to use +.BR "NFS over TCP where possible" , +since TCP does not perform fragmentation. +.P +If you absolutely have to use NFS over UDP over Gigabit Ethernet, +some steps can be taken to mitigate the problem and reduce the +probability of corruption: +.TP +1.5i +.I Jumbo frames: +Many Gigabit network cards are capable of transmitting +frames bigger than the 1500 byte limit of traditional Ethernet, typically +9000 bytes. Using jumbo frames of 9000 bytes will allow you to run NFS over +UDP at a page size of 8K without fragmentation. Of course, this is +only feasible if all involved stations support jumbo frames. +.IP +To enable a machine to send jumbo frames on cards that support it, +it is sufficient to configure the interface for a MTU value of 9000. +.TP +1.5i +.I Lower reassembly timeout: +By lowering this timeout below the time it takes the IP ID counter +to wrap around, incorrect reassembly of fragments can be prevented +as well. To do so, simply write the new timeout value (in seconds) +to the file +.BR /proc/sys/net/ipv4/ipfrag_time . +.IP +A value of 2 seconds will greatly reduce the probability of IPID clashes on +a single Gigabit link, while still allowing for a reasonable timeout +when receiving fragmented traffic from distant peers. .SH "DATA AND METADATA COHERENCE" Some modern cluster file systems provide perfect cache coherence among their clients. @@ -1416,7 +1491,7 @@ security flavor encrypts every RPC request to prevent data exposure during network transit; however, expect some performance impact when using integrity checking or encryption. -Similar support for other forms of cryptographic security (such as lipkey and SPKM3) +Similar support for other forms of cryptographic security is also available. .P The NFS version 4 protocol allows @@ -1561,10 +1636,10 @@ To ensure that the saved mount options are not erased during a remount, specify either the local mount directory, or the server hostname and export pathname, but not both, during a remount. For example, .P -.NF -.TA 2.5i +.nf +.ta 8n mount -o remount,ro /mnt -.FI +.fi .P merges the mount option .B ro diff --git a/utils/mount/nfs_mount.h b/utils/mount/nfs_mount.h index 2becfb1..ec30c9b 100644 --- a/utils/mount/nfs_mount.h +++ b/utils/mount/nfs_mount.h @@ -75,9 +75,6 @@ struct nfs_mount_data { #define AUTH_GSS_LKEY 390006 #define AUTH_GSS_LKEYI 390007 #define AUTH_GSS_LKEYP 390008 -#define AUTH_GSS_SPKM 390009 -#define AUTH_GSS_SPKMI 390010 -#define AUTH_GSS_SPKMP 390011 #endif int nfsmount(const char *, const char *, int , char **, int, int); diff --git a/utils/mount/nfsmount.c b/utils/mount/nfsmount.c index 1298fe4..930622d 100644 --- a/utils/mount/nfsmount.c +++ b/utils/mount/nfsmount.c @@ -294,18 +294,6 @@ parse_options(char *old_opts, struct nfs_mount_data *data, data->pseudoflavor = AUTH_GSS_KRB5I; else if (!strcmp(secflavor, "krb5p")) data->pseudoflavor = AUTH_GSS_KRB5P; - else if (!strcmp(secflavor, "lipkey")) - data->pseudoflavor = AUTH_GSS_LKEY; - else if (!strcmp(secflavor, "lipkey-i")) - data->pseudoflavor = AUTH_GSS_LKEYI; - else if (!strcmp(secflavor, "lipkey-p")) - data->pseudoflavor = AUTH_GSS_LKEYP; - else if (!strcmp(secflavor, "spkm3")) - data->pseudoflavor = AUTH_GSS_SPKM; - else if (!strcmp(secflavor, "spkm3i")) - data->pseudoflavor = AUTH_GSS_SPKMI; - else if (!strcmp(secflavor, "spkm3p")) - data->pseudoflavor = AUTH_GSS_SPKMP; else if (sloppy) continue; else { diff --git a/utils/mount/stropts.c b/utils/mount/stropts.c index 314a806..e09aa7c 100644 --- a/utils/mount/stropts.c +++ b/utils/mount/stropts.c @@ -540,6 +540,8 @@ nfs_rewrite_pmap_mount_options(struct mount_options *options) errno = EOPNOTSUPP; else if (rpc_createerr.cf_stat == RPC_AUTHERROR) errno = EACCES; + else if (rpc_createerr.cf_stat == RPC_TIMEDOUT) + errno = ETIMEDOUT; else if (rpc_createerr.cf_error.re_errno != 0) errno = rpc_createerr.cf_error.re_errno; return 0; @@ -665,9 +667,10 @@ static int nfs_try_mount_v3v2(struct nfsmount_info *mi) case EHOSTUNREACH: continue; default: - break; + goto out; } } +out: return ret; } @@ -751,9 +754,10 @@ static int nfs_try_mount_v4(struct nfsmount_info *mi) case EHOSTUNREACH: continue; default: - break; + goto out; } } +out: return ret; } @@ -907,7 +911,8 @@ static int nfsmount_parent(struct nfsmount_info *mi) if (nfs_try_mount(mi)) return EX_SUCCESS; - if (nfs_is_permanent_error(errno)) { + /* retry background mounts when the server is not up */ + if (nfs_is_permanent_error(errno) && errno != EOPNOTSUPP) { mount_error(mi->spec, mi->node, errno); return EX_FAIL; } @@ -942,7 +947,8 @@ static int nfsmount_child(struct nfsmount_info *mi) if (nfs_try_mount(mi)) return EX_SUCCESS; - if (nfs_is_permanent_error(errno)) + /* retry background mounts when the server is not up */ + if (nfs_is_permanent_error(errno) && errno != EOPNOTSUPP) break; if (time(NULL) > timeout) diff --git a/utils/mountd/Makefile.am b/utils/mountd/Makefile.am index eba81fc..7db968b 100644 --- a/utils/mountd/Makefile.am +++ b/utils/mountd/Makefile.am @@ -12,7 +12,7 @@ mountd_SOURCES = mountd.c mount_dispatch.c auth.c rmtab.c cache.c \ mountd_LDADD = ../../support/export/libexport.a \ ../../support/nfs/libnfs.a \ ../../support/misc/libmisc.a \ - $(LIBBSD) $(LIBWRAP) $(LIBNSL) $(LIBBLKID) + $(LIBBSD) $(LIBWRAP) $(LIBNSL) $(LIBBLKID) $(LIBDL) $(LIBTIRPC) mountd_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS) \ -I$(top_builddir)/support/include \ -I$(top_srcdir)/support/export diff --git a/utils/mountd/auth.c b/utils/mountd/auth.c index ccc849a..508040a 100644 --- a/utils/mountd/auth.c +++ b/utils/mountd/auth.c @@ -112,15 +112,23 @@ auth_reload() return counter; } +static char *get_client_ipaddr_name(const struct sockaddr *caller) +{ + char buf[INET6_ADDRSTRLEN + 1]; + + buf[0] = '$'; + host_ntop(caller, buf + 1, sizeof(buf) - 1); + return strdup(buf); +} + static char * get_client_hostname(const struct sockaddr *caller, struct addrinfo *ai, enum auth_error *error) { - char buf[INET6_ADDRSTRLEN]; char *n; if (use_ipaddr) - return strdup(host_ntop(caller, buf, sizeof(buf))); + return get_client_ipaddr_name(caller); n = client_compose(ai); *error = unknown_host; if (!n) @@ -131,6 +139,23 @@ get_client_hostname(const struct sockaddr *caller, struct addrinfo *ai, return strdup("DEFAULT"); } +bool ipaddr_client_matches(nfs_export *exp, struct addrinfo *ai) +{ + return client_check(exp->m_client, ai); +} + +bool namelist_client_matches(nfs_export *exp, char *dom) +{ + return client_member(dom, exp->m_client->m_hostname); +} + +bool client_matches(nfs_export *exp, char *dom, struct addrinfo *ai) +{ + if (is_ipaddr_client(dom)) + return ipaddr_client_matches(exp, ai); + return namelist_client_matches(exp, dom); +} + /* return static nfs_export with details filled in */ static nfs_export * auth_authenticate_newcache(const struct sockaddr *caller, @@ -155,9 +180,10 @@ auth_authenticate_newcache(const struct sockaddr *caller, for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { if (strcmp(path, exp->m_export.e_path)) continue; - if (!use_ipaddr && !client_member(my_client.m_hostname, exp->m_client->m_hostname)) + if (!client_matches(exp, my_client.m_hostname, ai)) continue; - if (use_ipaddr && !client_check(exp->m_client, ai)) + if (exp->m_export.e_flags & NFSEXP_V4ROOT) + /* not acceptable for v[23] export */ continue; break; } @@ -187,10 +213,6 @@ auth_authenticate_internal(const struct sockaddr *caller, const char *path, return NULL; } } - if (exp->m_export.e_flags & NFSEXP_V4ROOT) { - *error = no_entry; - return NULL; - } if (!(exp->m_export.e_flags & NFSEXP_INSECURE_PORT) && nfs_get_port(caller) >= IPPORT_RESERVED) { *error = illegal_port; diff --git a/utils/mountd/cache.c b/utils/mountd/cache.c index d2ae456..7d80432 100644 --- a/utils/mountd/cache.c +++ b/utils/mountd/cache.c @@ -84,7 +84,6 @@ static void auth_unix_ip(FILE *f) char ipaddr[INET6_ADDRSTRLEN]; char *client = NULL; struct addrinfo *tmp = NULL; - struct addrinfo *ai = NULL; if (readline(fileno(f), &lbuf, &lbuflen) != 1) return; @@ -107,12 +106,16 @@ static void auth_unix_ip(FILE *f) /* addr is a valid, interesting address, find the domain name... */ if (!use_ipaddr) { + struct addrinfo *ai = NULL; + ai = client_resolve(tmp->ai_addr); + if (ai == NULL) + goto out; client = client_compose(ai); freeaddrinfo(ai); + if (!client) + goto out; } - freeaddrinfo(tmp); - qword_print(f, "nfsd"); qword_print(f, ipaddr); qword_printuint(f, time(0) + DEFAULT_TTL); @@ -124,6 +127,9 @@ static void auth_unix_ip(FILE *f) xlog(D_CALL, "auth_unix_ip: client %p '%s'", client, client?client: "DEFAULT"); free(client); +out: + freeaddrinfo(tmp); + } static void auth_unix_gid(FILE *f) @@ -495,6 +501,21 @@ static bool match_fsid(struct parsed_fsid *parsed, nfs_export *exp, char *path) return false; } +struct addrinfo *lookup_client_addr(char *dom) +{ + struct addrinfo *ret; + struct addrinfo *tmp; + + dom++; /* skip initial "$" */ + + tmp = host_pton(dom); + if (tmp == NULL) + return NULL; + ret = client_resolve(tmp->ai_addr); + freeaddrinfo(tmp); + return ret; +} + static void nfsd_fh(FILE *f) { /* request are: @@ -538,6 +559,12 @@ static void nfsd_fh(FILE *f) auth_reload(); + if (is_ipaddr_client(dom)) { + ai = lookup_client_addr(dom); + if (!ai) + goto out; + } + /* Now determine export point for this fsid/domain */ for (i=0 ; i < MCL_MAXTYPES; i++) { nfs_export *next_exp; @@ -568,7 +595,8 @@ static void nfsd_fh(FILE *f) next_exp = exp->m_next; } - if (!use_ipaddr && !client_member(dom, exp->m_client->m_hostname)) + if (!is_ipaddr_client(dom) + && !namelist_client_matches(exp, dom)) continue; if (exp->m_export.e_mountpoint && !is_mountpoint(exp->m_export.e_mountpoint[0]? @@ -578,29 +606,29 @@ static void nfsd_fh(FILE *f) if (!match_fsid(&parsed, exp, path)) continue; - if (use_ipaddr) { - if (ai == NULL) { - struct addrinfo *tmp; - tmp = host_pton(dom); - if (tmp == NULL) - goto out; - ai = client_resolve(tmp->ai_addr); - freeaddrinfo(tmp); - } - if (!client_check(exp->m_client, ai)) - continue; - } + if (is_ipaddr_client(dom) + && !ipaddr_client_matches(exp, ai)) + continue; if (!found || subexport(&exp->m_export, found)) { found = &exp->m_export; free(found_path); found_path = strdup(path); if (found_path == NULL) goto out; - } else if (strcmp(found->e_path, exp->m_export.e_path) + } else if (strcmp(found->e_path, exp->m_export.e_path) != 0 && !subexport(found, &exp->m_export)) { xlog(L_WARNING, "%s and %s have same filehandle for %s, using first", found_path, path, dom); + } else { + /* same path, if one is V4ROOT, choose the other */ + if (found->e_flags & NFSEXP_V4ROOT) { + found = &exp->m_export; + free(found_path); + found_path = strdup(path); + if (found_path == NULL) + goto out; + } } } } @@ -741,14 +769,6 @@ static int path_matches(nfs_export *exp, char *path) return strcmp(path, exp->m_export.e_path) == 0; } -static int -client_matches(nfs_export *exp, char *dom, struct addrinfo *ai) -{ - if (use_ipaddr) - return client_check(exp->m_client, ai); - return client_member(dom, exp->m_client->m_hostname); -} - static int export_matches(nfs_export *exp, char *dom, char *path, struct addrinfo *ai) { @@ -772,10 +792,14 @@ lookup_export(char *dom, char *path, struct addrinfo *ai) found_type = i; continue; } - - /* Always prefer non-V4ROOT mounts */ - if (found->m_export.e_flags & NFSEXP_V4ROOT) + /* Always prefer non-V4ROOT exports */ + if (exp->m_export.e_flags & NFSEXP_V4ROOT) + continue; + if (found->m_export.e_flags & NFSEXP_V4ROOT) { + found = exp; + found_type = i; continue; + } /* If one is a CROSSMOUNT, then prefer the longest path */ if (((found->m_export.e_flags & NFSEXP_CROSSMOUNT) || @@ -802,6 +826,229 @@ lookup_export(char *dom, char *path, struct addrinfo *ai) return found; } +#ifdef HAVE_NFS_PLUGIN_H +#include +#include + +/* + * Walk through a set of FS locations and build a set of export options. + * Returns true if all went to plan; otherwise, false. + */ +static _Bool +locations_to_options(struct jp_ops *ops, nfs_fsloc_set_t locations, + char *options, size_t remaining, int *ttl) +{ + char *server, *last_path, *rootpath, *ptr; + _Bool seen = false; + + last_path = NULL; + rootpath = NULL; + server = NULL; + ptr = options; + *ttl = 0; + + for (;;) { + enum jp_status status; + int len; + + status = ops->jp_get_next_location(locations, &server, + &rootpath, ttl); + if (status == JP_EMPTY) + break; + if (status != JP_OK) { + xlog(D_GENERAL, "%s: failed to parse location: %s", + __func__, ops->jp_error(status)); + goto out_false; + } + xlog(D_GENERAL, "%s: Location: %s:%s", + __func__, server, rootpath); + + if (last_path && strcmp(rootpath, last_path) == 0) { + len = snprintf(ptr, remaining, "+%s", server); + if (len < 0) { + xlog(D_GENERAL, "%s: snprintf: %m", __func__); + goto out_false; + } + if ((size_t)len >= remaining) { + xlog(D_GENERAL, "%s: options buffer overflow", __func__); + goto out_false; + } + remaining -= (size_t)len; + ptr += len; + } else { + if (last_path == NULL) + len = snprintf(ptr, remaining, "refer=%s@%s", + rootpath, server); + else + len = snprintf(ptr, remaining, ":%s@%s", + rootpath, server); + if (len < 0) { + xlog(D_GENERAL, "%s: snprintf: %m", __func__); + goto out_false; + } + if ((size_t)len >= remaining) { + xlog(D_GENERAL, "%s: options buffer overflow", + __func__); + goto out_false; + } + remaining -= (size_t)len; + ptr += len; + last_path = rootpath; + } + + seen = true; + free(rootpath); + free(server); + } + + xlog(D_CALL, "%s: options='%s', ttl=%d", + __func__, options, *ttl); + return seen; + +out_false: + free(rootpath); + free(server); + return false; +} + +/* + * Walk through the set of FS locations and build an exportent. + * Returns pointer to an exportent if "junction" refers to a junction. + * + * Returned exportent points to static memory. + */ +static struct exportent *do_locations_to_export(struct jp_ops *ops, + nfs_fsloc_set_t locations, const char *junction, + char *options, size_t options_len) +{ + struct exportent *exp; + int ttl; + + if (!locations_to_options(ops, locations, options, options_len, &ttl)) + return NULL; + + exp = mkexportent("*", (char *)junction, options); + if (exp == NULL) { + xlog(L_ERROR, "%s: Failed to construct exportent", __func__); + return NULL; + } + + exp->e_uuid = NULL; + exp->e_ttl = ttl; + return exp; +} + +/* + * Convert set of FS locations to an exportent. Returns pointer to + * an exportent if "junction" refers to a junction. + * + * Returned exportent points to static memory. + */ +static struct exportent *locations_to_export(struct jp_ops *ops, + nfs_fsloc_set_t locations, const char *junction) +{ + struct exportent *exp; + char *options; + + options = malloc(BUFSIZ); + if (options == NULL) { + xlog(D_GENERAL, "%s: failed to allocate options buffer", + __func__); + return NULL; + } + options[0] = '\0'; + + exp = do_locations_to_export(ops, locations, junction, + options, BUFSIZ); + + free(options); + return exp; +} + +/* + * Retrieve locations information in "junction" and dump it to the + * kernel. Returns pointer to an exportent if "junction" refers + * to a junction. + * + * Returned exportent points to static memory. + */ +static struct exportent *invoke_junction_ops(void *handle, + const char *junction) +{ + nfs_fsloc_set_t locations; + struct exportent *exp; + enum jp_status status; + struct jp_ops *ops; + char *error; + + ops = (struct jp_ops *)dlsym(handle, "nfs_junction_ops"); + error = dlerror(); + if (error != NULL) { + xlog(D_GENERAL, "%s: dlsym(jp_junction_ops): %s", + __func__, error); + return NULL; + } + if (ops->jp_api_version != JP_API_VERSION) { + xlog(D_GENERAL, "%s: unrecognized junction API version: %u", + __func__, ops->jp_api_version); + return NULL; + } + + status = ops->jp_init(false); + if (status != JP_OK) { + xlog(D_GENERAL, "%s: failed to resolve %s: %s", + __func__, junction, ops->jp_error(status)); + return NULL; + } + + status = ops->jp_get_locations(junction, &locations); + if (status != JP_OK) { + xlog(D_GENERAL, "%s: failed to resolve %s: %s", + __func__, junction, ops->jp_error(status)); + return NULL; + } + + exp = locations_to_export(ops, locations, junction); + + ops->jp_put_locations(locations); + ops->jp_done(); + return exp; +} + +/* + * Load the junction plug-in, then try to resolve "pathname". + * Returns pointer to an initialized exportent if "junction" + * refers to a junction, or NULL if not. + * + * Returned exportent points to static memory. + */ +static struct exportent *lookup_junction(const char *pathname) +{ + struct exportent *exp; + void *handle; + + handle = dlopen("libnfsjunct.so", RTLD_NOW); + if (handle == NULL) { + xlog(D_GENERAL, "%s: dlopen: %s", __func__, dlerror()); + return NULL; + } + (void)dlerror(); /* Clear any error */ + + exp = invoke_junction_ops(handle, pathname); + + /* We could leave it loaded to make junction resolution + * faster next time. However, if we want to replace the + * library, that would require restarting mountd. */ + (void)dlclose(handle); + return exp; +} +#else /* !HAVE_NFS_PLUGIN_H */ +static inline struct exportent *lookup_junction(const char *UNUSED(pathname)) +{ + return NULL; +} +#endif /* !HAVE_NFS_PLUGIN_H */ + static void nfsd_export(FILE *f) { /* requests are: @@ -834,13 +1081,9 @@ static void nfsd_export(FILE *f) auth_reload(); - if (use_ipaddr) { - struct addrinfo *tmp; - tmp = host_pton(dom); - if (tmp == NULL) - goto out; - ai = client_resolve(tmp->ai_addr); - freeaddrinfo(tmp); + if (is_ipaddr_client(dom)) { + ai = lookup_client_addr(dom); + if (!ai) goto out; } @@ -854,7 +1097,7 @@ static void nfsd_export(FILE *f) dump_to_cache(f, dom, path, NULL); } } else { - dump_to_cache(f, dom, path, NULL); + dump_to_cache(f, dom, path, lookup_junction(path)); } out: xlog(D_CALL, "nfsd_export: found %p path %s", found, path ? path : NULL); diff --git a/utils/mountd/fsloc.c b/utils/mountd/fsloc.c index e2add2d..bc737d1 100644 --- a/utils/mountd/fsloc.c +++ b/utils/mountd/fsloc.c @@ -40,12 +40,12 @@ static void replicas_print(struct servers *sp) { int i; if (!sp) { - xlog(L_NOTICE, "NULL replicas pointer\n"); + xlog(L_NOTICE, "NULL replicas pointer"); return; } - xlog(L_NOTICE, "replicas listsize=%i\n", sp->h_num); + xlog(L_NOTICE, "replicas listsize=%i", sp->h_num); for (i=0; ih_num; i++) { - xlog(L_NOTICE, " %s:%s\n", + xlog(L_NOTICE, " %s:%s", sp->h_mp[i]->h_host, sp->h_mp[i]->h_path); } } @@ -120,23 +120,37 @@ static struct servers *parse_list(char **list) */ static struct servers *method_list(char *data) { - char *copy, *ptr=data; + char *copy, *ptr=data, *p; char **list; int i, listsize; struct servers *rv=NULL; + bool v6esc = false; - xlog(L_NOTICE, "method_list(%s)\n", data); + xlog(L_NOTICE, "method_list(%s)", data); for (ptr--, listsize=1; ptr; ptr=index(ptr, ':'), listsize++) ptr++; list = malloc(listsize * sizeof(char *)); copy = strdup(data); if (copy) - xlog(L_NOTICE, "converted to %s\n", copy); + xlog(L_NOTICE, "converted to %s", copy); if (list && copy) { ptr = copy; - for (i=0; im_export; dupexportent(&eep, &pseudo_root.m_export); - eep.e_hostname = strdup(curexp->e_hostname); + eep.e_hostname = curexp->e_hostname; strncpy(eep.e_path, path, sizeof(eep.e_path)); if (strcmp(path, "/") != 0) eep.e_flags &= ~NFSEXP_FSID; @@ -149,13 +150,13 @@ static int v4root_add_parents(nfs_export *exp) "pseudo export for '%s'", exp->m_export.e_path); return -ENOMEM; } - for (ptr = path + 1; ptr; ptr = strchr(ptr, '/')) { + for (ptr = path; ptr; ptr = strchr(ptr, '/')) { int ret; char saved; saved = *ptr; *ptr = '\0'; - ret = pseudofs_update(hostname, path, exp); + ret = pseudofs_update(hostname, *path ? path : "/", exp); if (ret) return ret; *ptr = saved; @@ -192,6 +193,13 @@ v4root_set() */ continue; + if (strcmp(exp->m_export.e_path, "/") == 0 && + !(exp->m_export.e_flags & NFSEXP_FSID)) { + /* Force '/' to be exported as fsid == 0*/ + exp->m_export.e_flags |= NFSEXP_FSID; + exp->m_export.e_fsid = 0; + } + v4root_add_parents(exp); /* XXX: error handling! */ } diff --git a/utils/nfsd/Makefile.am b/utils/nfsd/Makefile.am index c4c6fb0..1536065 100644 --- a/utils/nfsd/Makefile.am +++ b/utils/nfsd/Makefile.am @@ -8,7 +8,7 @@ KPREFIX = @kprefix@ sbin_PROGRAMS = nfsd nfsd_SOURCES = nfsd.c nfssvc.c -nfsd_LDADD = ../../support/nfs/libnfs.a +nfsd_LDADD = ../../support/nfs/libnfs.a $(LIBTIRPC) MAINTAINERCLEANFILES = Makefile.in diff --git a/utils/nfsd/nfsd.c b/utils/nfsd/nfsd.c index 8bc5d3a..2a3f5cc 100644 --- a/utils/nfsd/nfsd.c +++ b/utils/nfsd/nfsd.c @@ -27,6 +27,10 @@ #include "nfssvc.h" #include "xlog.h" +#ifndef NFSD_NPROC +#define NFSD_NPROC 8 +#endif + static void usage(const char *); static struct option longopts[] = @@ -90,7 +94,7 @@ nfsd_enable_protos(unsigned int *proto4, unsigned int *proto6) int main(int argc, char **argv) { - int count = 1, c, error = 0, portnum = 0, fd, found_one; + int count = NFSD_NPROC, c, error = 0, portnum = 0, fd, found_one; char *p, *progname, *port; char *haddr = NULL; int socket_up = 0; diff --git a/utils/nfsd/nfsd.man b/utils/nfsd/nfsd.man index d8988d2..1cf9296 100644 --- a/utils/nfsd/nfsd.man +++ b/utils/nfsd/nfsd.man @@ -38,7 +38,7 @@ request on all known network addresses. This may change in future releases of the Linux Kernel. .TP .B \-p " or " \-\-port port -specify a diferent port to listen on for NFS requests. By default, +specify a different port to listen on for NFS requests. By default, .B rpc.nfsd will listen on port 2049. .TP diff --git a/utils/nfsdcld/Makefile.am b/utils/nfsdcld/Makefile.am new file mode 100644 index 0000000..073a71b --- /dev/null +++ b/utils/nfsdcld/Makefile.am @@ -0,0 +1,14 @@ +## Process this file with automake to produce Makefile.in + +man8_MANS = nfsdcld.man +EXTRA_DIST = $(man8_MANS) + +AM_CFLAGS += -D_LARGEFILE64_SOURCE +sbin_PROGRAMS = nfsdcld + +nfsdcld_SOURCES = nfsdcld.c sqlite.c + +nfsdcld_LDADD = ../../support/nfs/libnfs.a $(LIBEVENT) $(LIBSQLITE) $(LIBCAP) + +MAINTAINERCLEANFILES = Makefile.in + diff --git a/utils/nfsdcld/nfsdcld.c b/utils/nfsdcld/nfsdcld.c new file mode 100644 index 0000000..e7af4e3 --- /dev/null +++ b/utils/nfsdcld/nfsdcld.c @@ -0,0 +1,607 @@ +/* + * nfsdcld.c -- NFSv4 client name tracking daemon + * + * Copyright (C) 2011 Red Hat, Jeff Layton + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_CAPABILITY_H +#include +#include +#endif + +#include "xlog.h" +#include "nfslib.h" +#include "cld.h" +#include "sqlite.h" + +#ifndef PIPEFS_DIR +#define PIPEFS_DIR NFS_STATEDIR "/rpc_pipefs" +#endif + +#define DEFAULT_CLD_PATH PIPEFS_DIR "/nfsd/cld" + +#ifndef CLD_DEFAULT_STORAGEDIR +#define CLD_DEFAULT_STORAGEDIR NFS_STATEDIR "/nfsdcld" +#endif + +#define UPCALL_VERSION 1 + +/* private data structures */ +struct cld_client { + int cl_fd; + struct event cl_event; + struct cld_msg cl_msg; +}; + +/* global variables */ +static char *pipepath = DEFAULT_CLD_PATH; +static int inotify_fd = -1; +static struct event pipedir_event; + +static struct option longopts[] = +{ + { "help", 0, NULL, 'h' }, + { "foreground", 0, NULL, 'F' }, + { "debug", 0, NULL, 'd' }, + { "pipe", 1, NULL, 'p' }, + { "storagedir", 1, NULL, 's' }, + { NULL, 0, 0, 0 }, +}; + +/* forward declarations */ +static void cldcb(int UNUSED(fd), short which, void *data); + +static void +usage(char *progname) +{ + printf("%s [ -hFd ] [ -p pipe ] [ -s dir ]\n", progname); +} + +static int +cld_set_caps(void) +{ + int ret = 0; +#ifdef HAVE_SYS_CAPABILITY_H + unsigned long i; + cap_t caps; + + if (getuid() != 0) { + xlog(L_ERROR, "Not running as root. Daemon won't be able to " + "open the pipe after dropping capabilities!"); + return -EINVAL; + } + + /* prune the bounding set to nothing */ + for (i = 0; i <= CAP_LAST_CAP; ++i) { + ret = prctl(PR_CAPBSET_DROP, i); + if (ret) { + xlog(L_ERROR, "Unable to prune capability %lu from " + "bounding set: %m", i); + return -errno; + } + } + + /* get a blank capset */ + caps = cap_init(); + if (caps == NULL) { + xlog(L_ERROR, "Unable to get blank capability set: %m"); + return -errno; + } + + /* reset the process capabilities */ + if (cap_set_proc(caps) != 0) { + xlog(L_ERROR, "Unable to set process capabilities: %m"); + ret = -errno; + } + cap_free(caps); +#endif + return ret; +} + +#define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX) + +static int +cld_pipe_open(struct cld_client *clnt) +{ + int fd; + + xlog(D_GENERAL, "%s: opening upcall pipe %s", __func__, pipepath); + fd = open(pipepath, O_RDWR, 0); + if (fd < 0) { + xlog(D_GENERAL, "%s: open of %s failed: %m", __func__, pipepath); + return -errno; + } + + if (clnt->cl_event.ev_flags & EVLIST_INIT) + event_del(&clnt->cl_event); + if (clnt->cl_fd >= 0) + close(clnt->cl_fd); + + clnt->cl_fd = fd; + event_set(&clnt->cl_event, clnt->cl_fd, EV_READ, cldcb, clnt); + /* event_add is done by the caller */ + return 0; +} + +static void +cld_inotify_cb(int UNUSED(fd), short which, void *data) +{ + int ret; + size_t elen; + ssize_t rret; + char evbuf[INOTIFY_EVENT_MAX]; + char *dirc = NULL, *pname; + struct inotify_event *event = (struct inotify_event *)evbuf; + struct cld_client *clnt = data; + + if (which != EV_READ) + return; + + xlog(D_GENERAL, "%s: called for EV_READ", __func__); + + dirc = strndup(pipepath, PATH_MAX); + if (!dirc) { + xlog(L_ERROR, "%s: unable to allocate memory", __func__); + goto out; + } + + rret = read(inotify_fd, evbuf, INOTIFY_EVENT_MAX); + if (rret < 0) { + xlog(L_ERROR, "%s: read from inotify fd failed: %m", __func__); + goto out; + } + + /* check to see if we have a filename in the evbuf */ + if (!event->len) { + xlog(D_GENERAL, "%s: no filename in inotify event", __func__); + goto out; + } + + pname = basename(dirc); + elen = strnlen(event->name, event->len); + + /* does the filename match our pipe? */ + if (strlen(pname) != elen || memcmp(pname, event->name, elen)) { + xlog(D_GENERAL, "%s: wrong filename (%s)", __func__, + event->name); + goto out; + } + + ret = cld_pipe_open(clnt); + switch (ret) { + case 0: + /* readd the event for the cl_event pipe */ + event_add(&clnt->cl_event, NULL); + break; + case -ENOENT: + /* pipe must have disappeared, wait for it to come back */ + goto out; + default: + /* anything else is fatal */ + xlog(L_FATAL, "%s: unable to open new pipe (%d). Aborting.", + ret, __func__); + exit(ret); + } + +out: + event_add(&pipedir_event, NULL); + free(dirc); +} + +static int +cld_inotify_setup(void) +{ + int ret; + char *dirc, *dname; + + dirc = strndup(pipepath, PATH_MAX); + if (!dirc) { + xlog_err("%s: unable to allocate memory", __func__); + ret = -ENOMEM; + goto out_free; + } + + dname = dirname(dirc); + + inotify_fd = inotify_init(); + if (inotify_fd < 0) { + xlog_err("%s: inotify_init failed: %m", __func__); + ret = -errno; + goto out_free; + } + + ret = inotify_add_watch(inotify_fd, dname, IN_CREATE); + if (ret < 0) { + xlog_err("%s: inotify_add_watch failed: %m", __func__); + ret = -errno; + goto out_err; + } + +out_free: + free(dirc); + return 0; +out_err: + close(inotify_fd); + goto out_free; +} + +/* + * Set an inotify watch on the directory that should contain the pipe, and then + * try to open it. If it fails with anything but -ENOENT, return the error + * immediately. + * + * If it succeeds, then set up the pipe event handler. At that point, set up + * the inotify event handler and go ahead and return success. + */ +static int +cld_pipe_init(struct cld_client *clnt) +{ + int ret; + + xlog(D_GENERAL, "%s: init pipe handlers", __func__); + + ret = cld_inotify_setup(); + if (ret != 0) + goto out; + + clnt->cl_fd = -1; + ret = cld_pipe_open(clnt); + switch (ret) { + case 0: + /* add the event and we're good to go */ + event_add(&clnt->cl_event, NULL); + break; + case -ENOENT: + /* ignore this error -- cld_inotify_cb will handle it */ + ret = 0; + break; + default: + /* anything else is fatal */ + close(inotify_fd); + goto out; + } + + /* set event for inotify read */ + event_set(&pipedir_event, inotify_fd, EV_READ, cld_inotify_cb, clnt); + event_add(&pipedir_event, NULL); +out: + return ret; +} + +static void +cld_not_implemented(struct cld_client *clnt) +{ + int ret; + ssize_t bsize, wsize; + struct cld_msg *cmsg = &clnt->cl_msg; + + xlog(D_GENERAL, "%s: downcalling with not implemented error", __func__); + + /* set up reply */ + cmsg->cm_status = -EOPNOTSUPP; + + bsize = sizeof(*cmsg); + + wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize); + if (wsize != bsize) + xlog(L_ERROR, "%s: problem writing to cld pipe (%ld): %m", + __func__, wsize); + + /* reopen pipe, just to be sure */ + ret = cld_pipe_open(clnt); + if (ret) { + xlog(L_FATAL, "%s: unable to reopen pipe: %d", __func__, ret); + exit(ret); + } +} + +static void +cld_create(struct cld_client *clnt) +{ + int ret; + ssize_t bsize, wsize; + struct cld_msg *cmsg = &clnt->cl_msg; + + xlog(D_GENERAL, "%s: create client record.", __func__); + + ret = sqlite_insert_client(cmsg->cm_u.cm_name.cn_id, + cmsg->cm_u.cm_name.cn_len); + + cmsg->cm_status = ret ? -EREMOTEIO : ret; + + bsize = sizeof(*cmsg); + + xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status); + wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize); + if (wsize != bsize) { + xlog(L_ERROR, "%s: problem writing to cld pipe (%ld): %m", + __func__, wsize); + ret = cld_pipe_open(clnt); + if (ret) { + xlog(L_FATAL, "%s: unable to reopen pipe: %d", + __func__, ret); + exit(ret); + } + } +} + +static void +cld_remove(struct cld_client *clnt) +{ + int ret; + ssize_t bsize, wsize; + struct cld_msg *cmsg = &clnt->cl_msg; + + xlog(D_GENERAL, "%s: remove client record.", __func__); + + ret = sqlite_remove_client(cmsg->cm_u.cm_name.cn_id, + cmsg->cm_u.cm_name.cn_len); + + cmsg->cm_status = ret ? -EREMOTEIO : ret; + + bsize = sizeof(*cmsg); + + xlog(D_GENERAL, "%s: downcall with status %d", __func__, + cmsg->cm_status); + wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize); + if (wsize != bsize) { + xlog(L_ERROR, "%s: problem writing to cld pipe (%ld): %m", + __func__, wsize); + ret = cld_pipe_open(clnt); + if (ret) { + xlog(L_FATAL, "%s: unable to reopen pipe: %d", + __func__, ret); + exit(ret); + } + } +} + +static void +cld_check(struct cld_client *clnt) +{ + int ret; + ssize_t bsize, wsize; + struct cld_msg *cmsg = &clnt->cl_msg; + + xlog(D_GENERAL, "%s: check client record", __func__); + + ret = sqlite_check_client(cmsg->cm_u.cm_name.cn_id, + cmsg->cm_u.cm_name.cn_len); + + /* set up reply */ + cmsg->cm_status = ret ? -EACCES : ret; + + bsize = sizeof(*cmsg); + + xlog(D_GENERAL, "%s: downcall with status %d", __func__, + cmsg->cm_status); + wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize); + if (wsize != bsize) { + xlog(L_ERROR, "%s: problem writing to cld pipe (%ld): %m", + __func__, wsize); + ret = cld_pipe_open(clnt); + if (ret) { + xlog(L_FATAL, "%s: unable to reopen pipe: %d", + __func__, ret); + exit(ret); + } + } +} + +static void +cld_gracedone(struct cld_client *clnt) +{ + int ret; + ssize_t bsize, wsize; + struct cld_msg *cmsg = &clnt->cl_msg; + + xlog(D_GENERAL, "%s: grace done. cm_gracetime=%ld", __func__, + cmsg->cm_u.cm_gracetime); + + ret = sqlite_remove_unreclaimed(cmsg->cm_u.cm_gracetime); + + /* set up reply: downcall with 0 status */ + cmsg->cm_status = ret ? -EREMOTEIO : ret; + + bsize = sizeof(*cmsg); + + xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status); + wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize); + if (wsize != bsize) { + xlog(L_ERROR, "%s: problem writing to cld pipe (%ld): %m", + __func__, wsize); + ret = cld_pipe_open(clnt); + if (ret) { + xlog(L_FATAL, "%s: unable to reopen pipe: %d", + __func__, ret); + exit(ret); + } + } +} + +static void +cldcb(int UNUSED(fd), short which, void *data) +{ + ssize_t len; + struct cld_client *clnt = data; + struct cld_msg *cmsg = &clnt->cl_msg; + + if (which != EV_READ) + goto out; + + len = atomicio(read, clnt->cl_fd, cmsg, sizeof(*cmsg)); + if (len <= 0) { + xlog(L_ERROR, "%s: pipe read failed: %m", __func__); + cld_pipe_open(clnt); + goto out; + } + + if (cmsg->cm_vers != UPCALL_VERSION) { + xlog(L_ERROR, "%s: unsupported upcall version: %hu", + cmsg->cm_vers); + cld_pipe_open(clnt); + goto out; + } + + switch(cmsg->cm_cmd) { + case Cld_Create: + cld_create(clnt); + break; + case Cld_Remove: + cld_remove(clnt); + break; + case Cld_Check: + cld_check(clnt); + break; + case Cld_GraceDone: + cld_gracedone(clnt); + break; + default: + xlog(L_WARNING, "%s: command %u is not yet implemented", + __func__, cmsg->cm_cmd); + cld_not_implemented(clnt); + } +out: + event_add(&clnt->cl_event, NULL); +} + +int +main(int argc, char **argv) +{ + char arg; + int rc = 0; + bool foreground = false; + char *progname; + char *storagedir = CLD_DEFAULT_STORAGEDIR; + struct cld_client clnt; + + memset(&clnt, 0, sizeof(clnt)); + + progname = strdup(basename(argv[0])); + if (!progname) { + fprintf(stderr, "%s: unable to allocate memory.\n", argv[0]); + return 1; + } + + event_init(); + xlog_syslog(0); + xlog_stderr(1); + + /* process command-line options */ + while ((arg = getopt_long(argc, argv, "hdFp:s:", longopts, + NULL)) != EOF) { + switch (arg) { + case 'd': + xlog_config(D_ALL, 1); + break; + case 'F': + foreground = true; + break; + case 'p': + pipepath = optarg; + break; + case 's': + storagedir = optarg; + break; + default: + usage(progname); + return 0; + } + } + + + xlog_open(progname); + if (!foreground) { + xlog_syslog(1); + xlog_stderr(0); + rc = daemon(0, 0); + if (rc) { + xlog(L_ERROR, "Unable to daemonize: %m"); + goto out; + } + } + + /* drop all capabilities */ + rc = cld_set_caps(); + if (rc) + goto out; + + /* + * now see if the storagedir is writable by root w/o CAP_DAC_OVERRIDE. + * If it isn't then give the user a warning but proceed as if + * everything is OK. If the DB has already been created, then + * everything might still work. If it doesn't exist at all, then + * assume that the maindb init will be able to create it. Fail on + * anything else. + */ + if (access(storagedir, W_OK) == -1) { + switch (errno) { + case EACCES: + xlog(L_WARNING, "Storage directory %s is not writable. " + "Should be owned by root and writable " + "by owner!", storagedir); + break; + case ENOENT: + /* ignore and assume that we can create dir as root */ + break; + default: + xlog(L_ERROR, "Unexpected error when checking access " + "on %s: %m", storagedir); + rc = -errno; + goto out; + } + } + + /* set up storage db */ + rc = sqlite_maindb_init(storagedir); + if (rc) { + xlog(L_ERROR, "Failed to open main database: %d", rc); + goto out; + } + + /* set up event handler */ + rc = cld_pipe_init(&clnt); + if (rc) + goto out; + + xlog(D_GENERAL, "%s: Starting event dispatch handler.", __func__); + rc = event_dispatch(); + if (rc < 0) + xlog(L_ERROR, "%s: event_dispatch failed: %m", __func__); + + close(clnt.cl_fd); + close(inotify_fd); +out: + free(progname); + return rc; +} diff --git a/utils/nfsdcld/nfsdcld.man b/utils/nfsdcld/nfsdcld.man new file mode 100644 index 0000000..9ddaf64 --- /dev/null +++ b/utils/nfsdcld/nfsdcld.man @@ -0,0 +1,185 @@ +.\" Automatically generated by Pod::Man 2.22 (Pod::Simple 3.13) +.\" +.\" Standard preamble: +.\" ======================================================================== +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +.\" Set up some character translations and predefined strings. \*(-- will +.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left +.\" double quote, and \*(R" will give a right double quote. \*(C+ will +.\" give a nicer C++. Capital omega is used to do unbreakable dashes and +.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, +.\" nothing in troff, for use with C<>. +.tr \(*W- +.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' +.ie n \{\ +. ds -- \(*W- +. ds PI pi +. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch +. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch +. ds L" "" +. ds R" "" +. ds C` "" +. ds C' "" +'br\} +.el\{\ +. ds -- \|\(em\| +. ds PI \(*p +. ds L" `` +. ds R" '' +'br\} +.\" +.\" Escape single quotes in literal strings from groff's Unicode transform. +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" +.\" If the F register is turned on, we'll generate index entries on stderr for +.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index +.\" entries marked with X<> in POD. Of course, you'll have to process the +.\" output yourself in some meaningful fashion. +.ie \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" +.. +. nr % 0 +. rr F +.\} +.el \{\ +. de IX +.. +.\} +.\" +.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). +.\" Fear. Run. Save yourself. No user-serviceable parts. +. \" fudge factors for nroff and troff +.if n \{\ +. ds #H 0 +. ds #V .8m +. ds #F .3m +. ds #[ \f1 +. ds #] \fP +.\} +.if t \{\ +. ds #H ((1u-(\\\\n(.fu%2u))*.13m) +. ds #V .6m +. ds #F 0 +. ds #[ \& +. ds #] \& +.\} +. \" simple accents for nroff and troff +.if n \{\ +. ds ' \& +. ds ` \& +. ds ^ \& +. ds , \& +. ds ~ ~ +. ds / +.\} +.if t \{\ +. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" +. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' +. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' +. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' +. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' +. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' +.\} +. \" troff and (daisy-wheel) nroff accents +.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' +.ds 8 \h'\*(#H'\(*b\h'-\*(#H' +.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] +.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' +.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' +.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] +.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] +.ds ae a\h'-(\w'a'u*4/10)'e +.ds Ae A\h'-(\w'A'u*4/10)'E +. \" corrections for vroff +.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' +.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' +. \" for low resolution devices (crt and lpr) +.if \n(.H>23 .if \n(.V>19 \ +\{\ +. ds : e +. ds 8 ss +. ds o a +. ds d- d\h'-1'\(ga +. ds D- D\h'-1'\(hy +. ds th \o'bp' +. ds Th \o'LP' +. ds ae ae +. ds Ae AE +.\} +.rm #[ #] #H #V #F C +.\" ======================================================================== +.\" +.IX Title "NFSDCLD 8" +.TH NFSDCLD 8 "2011-12-21" "" "" +.\" For nroff, turn off justification. Always turn off hyphenation; it makes +.\" way too many mistakes in technical documents. +.if n .ad l +.nh +.SH "NAME" +nfsdcld \- NFSv4 Client Tracking Daemon +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +nfsdcld [\-d] [\-F] [\-p path] [\-s stable storage dir] +.SH "DESCRIPTION" +.IX Header "DESCRIPTION" +nfsdcld is the NFSv4 client tracking daemon. It is not necessary to run +this daemon on machines that are not acting as NFSv4 servers. +.PP +When a network partition is combined with a server reboot, there are +edge conditions that can cause the server to grant lock reclaims when +other clients have taken conflicting locks in the interim. A more detailed +explanation of this issue is described in \s-1RFC\s0 3530, section 8.6.3. +.PP +In order to prevent these problems, the server must track a small amount +of per-client information on stable storage. This daemon provides the +userspace piece of that functionality. +.SH "OPTIONS" +.IX Header "OPTIONS" +.IP "\fB\-d\fR, \fB\-\-debug\fR" 4 +.IX Item "-d, --debug" +Enable debug level logging. +.IP "\fB\-F\fR, \fB\-\-foreground\fR" 4 +.IX Item "-F, --foreground" +Runs the daemon in the foreground and prints all output to stderr +.IP "\fB\-p\fR \fIpipe\fR, \fB\-\-pipe\fR=\fIpipe\fR" 4 +.IX Item "-p pipe, --pipe=pipe" +Location of the \*(L"cld\*(R" upcall pipe. The default value is +\&\fI/var/lib/nfs/rpc_pipefs/nfsd/cld\fR. If the pipe does not exist when the +daemon starts then it will wait for it to be created. +.IP "\fB\-s\fR \fIstorage_dir\fR, \fB\-\-storagedir\fR=\fIstorage_dir\fR" 4 +.IX Item "-s storagedir, --storagedir=storage_dir" +Directory where stable storage information should be kept. The default +value is \fI/var/lib/nfs/nfsdcld\fR. +.SH "NOTES" +.IX Header "NOTES" +The Linux kernel NFSv4 server has historically tracked this information +on stable storage by manipulating information on the filesystem +directly, in the directory to which \fI/proc/fs/nfsd/nfsv4recoverydir\fR +points. +.PP +This daemon requires a kernel that supports the nfsdcld upcall. If the +kernel does not support the new upcall, or is using the legacy client +name tracking code then it will not create the pipe that nfsdcld uses to +talk to the kernel. +.PP +This daemon should be run as root, as the pipe that it uses to communicate +with the kernel is only accessable by root. The daemon however does drop all +superuser capabilities after starting. Because of this, the \fIstoragedir\fR +should be owned by root, and be readable and writable by owner. +.SH "AUTHORS" +.IX Header "AUTHORS" +The nfsdcld daemon was developed by Jeff Layton . diff --git a/utils/nfsdcld/sqlite.c b/utils/nfsdcld/sqlite.c new file mode 100644 index 0000000..bb2519d --- /dev/null +++ b/utils/nfsdcld/sqlite.c @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2011 Red Hat, Jeff Layton + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/* + * Explanation: + * + * This file contains the code to manage the sqlite backend database for the + * clstated upcall daemon. + * + * The main database is called main.sqlite and contains the following tables: + * + * parameters: simple key/value pairs for storing database info + * + * clients: one column containing a BLOB with the as sent by the client + * and a timestamp (in epoch seconds) of when the record was + * established + * + * FIXME: should we also record the fsid being accessed? + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xlog.h" + +#define CLD_SQLITE_SCHEMA_VERSION 1 + +/* in milliseconds */ +#define CLD_SQLITE_BUSY_TIMEOUT 10000 + +/* private data structures */ + +/* global variables */ + +/* top level DB directory */ +static char *sqlite_topdir; + +/* reusable pathname and sql command buffer */ +static char buf[PATH_MAX]; + +/* global database handle */ +static sqlite3 *dbh; + +/* forward declarations */ + +/* make a directory, ignoring EEXIST errors unless it's not a directory */ +static int +mkdir_if_not_exist(char *dirname) +{ + int ret; + struct stat statbuf; + + ret = mkdir(dirname, S_IRWXU); + if (ret && errno != EEXIST) + return -errno; + + ret = stat(dirname, &statbuf); + if (ret) + return -errno; + + if (!S_ISDIR(statbuf.st_mode)) + ret = -ENOTDIR; + + return ret; +} + +/* + * Open the "main" database, and attempt to initialize it by creating the + * parameters table and inserting the schema version into it. Ignore any errors + * from that, and then attempt to select the version out of it again. If the + * version appears wrong, then assume that the DB is corrupt or has been + * upgraded, and return an error. If all of that works, then attempt to create + * the "clients" table. + */ +int +sqlite_maindb_init(char *topdir) +{ + int ret; + char *err = NULL; + sqlite3_stmt *stmt = NULL; + + sqlite_topdir = topdir; + + ret = mkdir_if_not_exist(sqlite_topdir); + if (ret) + return ret; + + ret = snprintf(buf, PATH_MAX - 1, "%s/main.sqlite", sqlite_topdir); + if (ret < 0) + return ret; + + buf[PATH_MAX - 1] = '\0'; + + ret = sqlite3_open(buf, &dbh); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "Unable to open main database: %d", ret); + return ret; + } + + ret = sqlite3_busy_timeout(dbh, CLD_SQLITE_BUSY_TIMEOUT); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "Unable to set sqlite busy timeout: %d", ret); + goto out_err; + } + + /* Try to create table */ + ret = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS parameters " + "(key TEXT PRIMARY KEY, value TEXT);", + NULL, NULL, &err); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "Unable to create parameter table: %d", ret); + goto out_err; + } + + /* insert version into table -- ignore error if it fails */ + ret = snprintf(buf, sizeof(buf), + "INSERT OR IGNORE INTO parameters values (\"version\", " + "\"%d\");", CLD_SQLITE_SCHEMA_VERSION); + if (ret < 0) { + goto out_err; + } else if ((size_t)ret >= sizeof(buf)) { + ret = -EINVAL; + goto out_err; + } + + ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "Unable to insert into parameter table: %d", + ret); + goto out_err; + } + + ret = sqlite3_prepare_v2(dbh, + "SELECT value FROM parameters WHERE key == \"version\";", + -1, &stmt, NULL); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "Unable to prepare select statement: %d", ret); + goto out_err; + } + + /* check schema version */ + ret = sqlite3_step(stmt); + if (ret != SQLITE_ROW) { + xlog(L_ERROR, "Select statement execution failed: %s", + sqlite3_errmsg(dbh)); + goto out_err; + } + + /* process SELECT result */ + ret = sqlite3_column_int(stmt, 0); + if (ret != CLD_SQLITE_SCHEMA_VERSION) { + xlog(L_ERROR, "Unsupported database schema version! " + "Expected %d, got %d.", + CLD_SQLITE_SCHEMA_VERSION, ret); + ret = -EINVAL; + goto out_err; + } + + /* now create the "clients" table */ + ret = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS clients " + "(id BLOB PRIMARY KEY, time INTEGER);", + NULL, NULL, &err); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "Unable to create clients table: %s", err); + goto out_err; + } + + sqlite3_free(err); + sqlite3_finalize(stmt); + return 0; + +out_err: + if (err) { + xlog(L_ERROR, "sqlite error: %s", err); + sqlite3_free(err); + } + sqlite3_finalize(stmt); + sqlite3_close(dbh); + return ret; +} + +/* + * Create a client record + * + * Returns a non-zero sqlite error code, or SQLITE_OK (aka 0) + */ +int +sqlite_insert_client(const unsigned char *clname, const size_t namelen) +{ + int ret; + sqlite3_stmt *stmt = NULL; + + ret = sqlite3_prepare_v2(dbh, "INSERT OR REPLACE INTO clients VALUES " + "(?, strftime('%s', 'now'));", -1, + &stmt, NULL); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "%s: insert statement prepare failed: %s", + __func__, sqlite3_errmsg(dbh)); + return ret; + } + + ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen, + SQLITE_STATIC); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "%s: bind blob failed: %s", __func__, + sqlite3_errmsg(dbh)); + goto out_err; + } + + ret = sqlite3_step(stmt); + if (ret == SQLITE_DONE) + ret = SQLITE_OK; + else + xlog(L_ERROR, "%s: unexpected return code from insert: %s", + __func__, sqlite3_errmsg(dbh)); + +out_err: + xlog(D_GENERAL, "%s: returning %d", __func__, ret); + sqlite3_finalize(stmt); + return ret; +} + +/* Remove a client record */ +int +sqlite_remove_client(const unsigned char *clname, const size_t namelen) +{ + int ret; + sqlite3_stmt *stmt = NULL; + + ret = sqlite3_prepare_v2(dbh, "DELETE FROM clients WHERE id==?", -1, + &stmt, NULL); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "%s: statement prepare failed: %s", + __func__, sqlite3_errmsg(dbh)); + goto out_err; + } + + ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen, + SQLITE_STATIC); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "%s: bind blob failed: %s", __func__, + sqlite3_errmsg(dbh)); + goto out_err; + } + + ret = sqlite3_step(stmt); + if (ret == SQLITE_DONE) + ret = SQLITE_OK; + else + xlog(L_ERROR, "%s: unexpected return code from delete: %d", + __func__, ret); + +out_err: + xlog(D_GENERAL, "%s: returning %d", __func__, ret); + sqlite3_finalize(stmt); + return ret; +} + +/* + * Is the given clname in the clients table? If so, then update its timestamp + * and return success. If the record isn't present, or the update fails, then + * return an error. + */ +int +sqlite_check_client(const unsigned char *clname, const size_t namelen) +{ + int ret; + sqlite3_stmt *stmt = NULL; + + ret = sqlite3_prepare_v2(dbh, "SELECT count(*) FROM clients WHERE " + "id==?", -1, &stmt, NULL); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "%s: unable to prepare update statement: %s", + __func__, sqlite3_errmsg(dbh)); + goto out_err; + } + + ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen, + SQLITE_STATIC); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "%s: bind blob failed: %s", + __func__, sqlite3_errmsg(dbh)); + goto out_err; + } + + ret = sqlite3_step(stmt); + if (ret != SQLITE_ROW) { + xlog(L_ERROR, "%s: unexpected return code from select: %d", + __func__, ret); + goto out_err; + } + + ret = sqlite3_column_int(stmt, 0); + xlog(D_GENERAL, "%s: select returned %d rows", ret); + if (ret != 1) { + ret = -EACCES; + goto out_err; + } + + sqlite3_finalize(stmt); + stmt = NULL; + ret = sqlite3_prepare_v2(dbh, "UPDATE OR FAIL clients SET " + "time=strftime('%s', 'now') WHERE id==?", + -1, &stmt, NULL); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "%s: unable to prepare update statement: %s", + __func__, sqlite3_errmsg(dbh)); + goto out_err; + } + + ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen, + SQLITE_STATIC); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "%s: bind blob failed: %s", + __func__, sqlite3_errmsg(dbh)); + goto out_err; + } + + ret = sqlite3_step(stmt); + if (ret == SQLITE_DONE) + ret = SQLITE_OK; + else + xlog(L_ERROR, "%s: unexpected return code from update: %s", + __func__, sqlite3_errmsg(dbh)); + +out_err: + xlog(D_GENERAL, "%s: returning %d", __func__, ret); + sqlite3_finalize(stmt); + return ret; +} + +/* + * remove any client records that were not reclaimed since grace_start. + */ +int +sqlite_remove_unreclaimed(time_t grace_start) +{ + int ret; + char *err = NULL; + + ret = snprintf(buf, sizeof(buf), "DELETE FROM clients WHERE time < %ld", + grace_start); + if (ret < 0) { + return ret; + } else if ((size_t)ret >= sizeof(buf)) { + ret = -EINVAL; + return ret; + } + + ret = sqlite3_exec(dbh, buf, NULL, NULL, &err); + if (ret != SQLITE_OK) + xlog(L_ERROR, "%s: delete failed: %s", __func__, err); + + xlog(D_GENERAL, "%s: returning %d", __func__, ret); + sqlite3_free(err); + return ret; +} diff --git a/utils/nfsdcld/sqlite.h b/utils/nfsdcld/sqlite.h new file mode 100644 index 0000000..c85e7d6 --- /dev/null +++ b/utils/nfsdcld/sqlite.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2011 Red Hat, Jeff Layton + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _SQLITE_H_ +#define _SQLITE_H_ + +int sqlite_maindb_init(char *topdir); +int sqlite_insert_client(const unsigned char *clname, const size_t namelen); +int sqlite_remove_client(const unsigned char *clname, const size_t namelen); +int sqlite_check_client(const unsigned char *clname, const size_t namelen); +int sqlite_remove_unreclaimed(const time_t grace_start); + +#endif /* _SQLITE_H */ diff --git a/utils/nfsidmap/Makefile.am b/utils/nfsidmap/Makefile.am index f837b91..c0675c4 100644 --- a/utils/nfsidmap/Makefile.am +++ b/utils/nfsidmap/Makefile.am @@ -4,6 +4,6 @@ man8_MANS = nfsidmap.man sbin_PROGRAMS = nfsidmap nfsidmap_SOURCES = nfsidmap.c -nfsidmap_LDADD = -lnfsidmap -lkeyutils +nfsidmap_LDADD = $(LIBNFSIDMAP) -lkeyutils ../../support/nfs/libnfs.a MAINTAINERCLEANFILES = Makefile.in diff --git a/utils/nfsidmap/nfsidmap.c b/utils/nfsidmap/nfsidmap.c index 2d87381..cf11551 100644 --- a/utils/nfsidmap/nfsidmap.c +++ b/utils/nfsidmap/nfsidmap.c @@ -3,21 +3,33 @@ #include #include #include +#include #include #include #include #include -#include +#include +#include "xlog.h" -/* gcc nfsidmap.c -o nfsidmap -l nfsidmap -l keyutils */ +int verbose = 0; +char *usage="Usage: %s [-v] [-c || [-u|-g|-r key] || [-t timeout] key desc]"; #define MAX_ID_LEN 11 #define IDMAP_NAMESZ 128 #define USER 1 #define GROUP 0 +#define PROCKEYS "/proc/keys" +#ifndef DEFAULT_KEYRING +#define DEFAULT_KEYRING "id_resolver" +#endif + +static int keyring_clear(char *keyring); + +#define UIDKEYS 0x1 +#define GIDKEYS 0x2 /* * Find either a user or group id based on the name@domain string @@ -36,9 +48,31 @@ int id_lookup(char *name_at_domain, key_serial_t key, int type) rc = nfs4_group_owner_to_gid(name_at_domain, &gid); sprintf(id, "%u", gid); } + if (rc < 0) + xlog_err("id_lookup: %s: failed: %m", + (type == USER ? "nfs4_owner_to_uid" : "nfs4_group_owner_to_gid")); - if (rc == 0) + if (rc == 0) { rc = keyctl_instantiate(key, id, strlen(id) + 1, 0); + if (rc < 0) { + switch(rc) { + case -EDQUOT: + case -ENFILE: + case -ENOMEM: + /* + * The keyring is full. Clear the keyring and try again + */ + rc = keyring_clear(DEFAULT_KEYRING); + if (rc == 0) + rc = keyctl_instantiate(key, id, strlen(id) + 1, 0); + break; + default: + break; + } + } + if (rc < 0) + xlog_err("id_lookup: keyctl_instantiate failed: %m"); + } return rc; } @@ -57,6 +91,7 @@ int name_lookup(char *id, key_serial_t key, int type) rc = nfs4_get_default_domain(NULL, domain, NFS4_MAX_DOMAIN_LEN); if (rc != 0) { rc = -1; + xlog_err("name_lookup: nfs4_get_default_domain failed: %m"); goto out; } @@ -67,39 +102,206 @@ int name_lookup(char *id, key_serial_t key, int type) gid = atoi(id); rc = nfs4_gid_to_name(gid, domain, name, IDMAP_NAMESZ); } + if (rc < 0) + xlog_err("name_lookup: %s: failed: %m", + (type == USER ? "nfs4_uid_to_name" : "nfs4_gid_to_name")); - if (rc == 0) + if (rc == 0) { rc = keyctl_instantiate(key, &name, strlen(name), 0); - + if (rc < 0) + xlog_err("name_lookup: keyctl_instantiate failed: %m"); + } out: return rc; } +/* + * Clear all the keys on the given keyring + */ +static int keyring_clear(char *keyring) +{ + FILE *fp; + char buf[BUFSIZ]; + key_serial_t key; + + if (keyring == NULL) + keyring = DEFAULT_KEYRING; + + if ((fp = fopen(PROCKEYS, "r")) == NULL) { + xlog_err("fopen(%s) failed: %m", PROCKEYS); + return 1; + } + + while(fgets(buf, BUFSIZ, fp) != NULL) { + if (strstr(buf, "keyring") == NULL) + continue; + if (strstr(buf, keyring) == NULL) + continue; + if (verbose) { + *(strchr(buf, '\n')) = '\0'; + xlog_warn("clearing '%s'", buf); + } + /* + * The key is the first arugment in the string + */ + *(strchr(buf, ' ')) = '\0'; + sscanf(buf, "%x", &key); + if (keyctl_clear(key) < 0) { + xlog_err("keyctl_clear(0x%x) failed: %m", key); + fclose(fp); + return 1; + } + fclose(fp); + return 0; + } + xlog_err("'%s' keyring was not found.", keyring); + fclose(fp); + return 1; +} +/* + * Revoke a key + */ +static int key_revoke(char *keystr, int keymask) +{ + FILE *fp; + char buf[BUFSIZ], *ptr; + key_serial_t key; + int mask; + + xlog_syslog(0); + + if ((fp = fopen(PROCKEYS, "r")) == NULL) { + xlog_err("fopen(%s) failed: %m", PROCKEYS); + return 1; + } + + while(fgets(buf, BUFSIZ, fp) != NULL) { + if (strstr(buf, "keyring") != NULL) + continue; + + mask = 0; + if ((ptr = strstr(buf, "uid:")) != NULL) + mask = UIDKEYS; + else if ((ptr = strstr(buf, "gid:")) != NULL) + mask = GIDKEYS; + else + continue; + + if ((keymask & mask) == 0) + continue; + + if (strncmp(ptr+4, keystr, strlen(keystr)) != 0) + continue; + + if (verbose) { + *(strchr(buf, '\n')) = '\0'; + xlog_warn("revoking '%s'", buf); + } + /* + * The key is the first arugment in the string + */ + *(strchr(buf, ' ')) = '\0'; + sscanf(buf, "%x", &key); + + if (keyctl_revoke(key) < 0) { + xlog_err("keyctl_revoke(0x%x) failed: %m", key); + fclose(fp); + return 1; + } + + keymask &= ~mask; + if (keymask == 0) { + fclose(fp); + return 0; + } + } + xlog_err("'%s' key was not found.", keystr); + fclose(fp); + return 1; +} int main(int argc, char **argv) { char *arg; char *value; char *type; - int rc = 1; + int rc = 1, opt; int timeout = 600; key_serial_t key; + char *progname, *keystr = NULL; + int clearing = 0, keymask = 0; + + /* Set the basename */ + if ((progname = strrchr(argv[0], '/')) != NULL) + progname++; + else + progname = argv[0]; - if (argc < 3) + xlog_open(progname); + + while ((opt = getopt(argc, argv, "u:g:r:ct:v")) != -1) { + switch (opt) { + case 'u': + keymask = UIDKEYS; + keystr = strdup(optarg); + break; + case 'g': + keymask = GIDKEYS; + keystr = strdup(optarg); + break; + case 'r': + keymask = GIDKEYS|UIDKEYS; + keystr = strdup(optarg); + break; + case 'c': + clearing++; + break; + case 'v': + verbose++; + break; + case 't': + timeout = atoi(optarg); + break; + default: + xlog_warn(usage, progname); + break; + } + } + + if (keystr) { + rc = key_revoke(keystr, keymask); + return rc; + } + if (clearing) { + xlog_syslog(0); + rc = keyring_clear(DEFAULT_KEYRING); + return rc; + } + + xlog_stderr(0); + if ((argc - optind) != 2) { + xlog_err("Bad arg count. Check /etc/request-key.conf"); + xlog_warn(usage, progname); return 1; + } + + if (verbose) + nfs4_set_debug(verbose, NULL); + + key = strtol(argv[optind++], NULL, 10); - arg = malloc(sizeof(char) * strlen(argv[2]) + 1); - strcpy(arg, argv[2]); + arg = strdup(argv[optind]); + if (arg == NULL) { + xlog_err("strdup failed: %m"); + return 1; + } type = strtok(arg, ":"); value = strtok(NULL, ":"); - if (argc == 4) { - timeout = atoi(argv[3]); - if (timeout < 0) - timeout = 0; + if (verbose) { + xlog_warn("key: 0x%lx type: %s value: %s timeout %ld", + key, type, value, timeout); } - key = strtol(argv[1], NULL, 10); - if (strcmp(type, "uid") == 0) rc = id_lookup(value, key, USER); else if (strcmp(type, "gid") == 0) @@ -109,7 +311,7 @@ int main(int argc, char **argv) else if (strcmp(type, "group") == 0) rc = name_lookup(value, key, GROUP); - /* Set timeout to 5 (600 seconds) minutes */ + /* Set timeout to 10 (600 seconds) minutes */ if (rc == 0) keyctl_set_timeout(key, timeout); diff --git a/utils/nfsidmap/nfsidmap.man b/utils/nfsidmap/nfsidmap.man index 2381908..3a3a523 100644 --- a/utils/nfsidmap/nfsidmap.man +++ b/utils/nfsidmap/nfsidmap.man @@ -5,6 +5,12 @@ .TH nfsidmap 5 "1 October 2010" .SH NAME nfsidmap \- The NFS idmapper upcall program +.SH SYNOPSIS +.B "nfsidmap [-v] [-t timeout] key desc" +.br +.B "nfsidmap [-v] [-c]" +.br +.B "nfsidmap [-v] [-u|-g|-r user]" .SH DESCRIPTION The file .I /usr/sbin/nfsidmap @@ -12,11 +18,36 @@ is used by the NFS idmapper to translate user and group ids into names, and to translate user and group names into ids. Idmapper uses request-key to perform the upcall and cache the result. .I /usr/sbin/nfsidmap -should only be called by request-key, and will perform the translation and +is called by /sbin/request-key, and will perform the translation and initialize a key with the resulting information. .PP -NFS_USE_NEW_IDMAPPER must be selected when configuring the kernel to use this -feature. +.I nfsidmap +can also used to clear the keyring of all the keys or +revoke one particular key. +This is useful when the id mappings have failed to due +to a lookup error resulting in all the cached uids/gids to be set +to the user id nobody. +.SH OPTIONS +.TP +.B -c +Clear the keyring of all the keys. +.TP +.B -g user +Revoke the gid key of the given user. +.TP +.B -r user +Revoke both the uid and gid key of the given user. +.TP +.B -t timeout +Set the expiration timer, in seconds, on the key. +The default is 600 seconds (10 mins). +.TP +.B -u user +Revoke the uid key of the given user. +.TP +.B -v +Increases the verbosity of the output to syslog +(can be specified multiple times). .SH CONFIGURING The file .I /etc/request-key.conf @@ -25,11 +56,13 @@ will need to be modified so can properly direct the upcall. The following line should be added before a call to keyctl negate: .PP -create id_resolver * * /usr/sbin/nfsidmap %k %d 600 +create id_resolver * * /usr/sbin/nfsidmap -t 600 %k %d .PP This will direct all id_resolver requests to the program -.I /usr/sbin/nfsidmap -The last parameter, 600, defines how many seconds into the future the key will +.I /usr/sbin/nfsidmap. +The +.B -t 600 +defines how many seconds into the future the key will expire. This is an optional parameter for .I /usr/sbin/nfsidmap and will default to 600 seconds when not specified. @@ -48,9 +81,9 @@ You can choose to handle any of these individually, rather than using the generic upcall program. If you would like to use your own program for a uid lookup then you would edit your request-key.conf so it looks similar to this: .PP -create id_resolver uid:* * /some/other/program %k %d 600 +create id_resolver uid:* * /some/other/program %k %d .br -create id_resolver * * /usr/sbin/nfsidmap %k %d 600 +create id_resolver * * /usr/sbin/nfsidmap %k %d .PP Notice that the new line was added above the line for the generic program. request-key will find the first matching line and run the corresponding program. diff --git a/utils/osd_login/Makefile.am b/utils/osd_login/Makefile.am new file mode 100644 index 0000000..adc493a --- /dev/null +++ b/utils/osd_login/Makefile.am @@ -0,0 +1,12 @@ +## Process this file with automake to produce Makefile.in + +OSD_LOGIN_FILES= osd_login + +EXTRA_DIST= $(OSD_LOGIN_FILES) + +all-local: $(OSD_LOGIN_FILES) + +install-data-hook: + $(INSTALL) --mode 755 osd_login $(DESTDIR)/sbin/osd_login + +MAINTAINERCLEANFILES = Makefile.in diff --git a/utils/osd_login/osd_login b/utils/osd_login/osd_login new file mode 100644 index 0000000..08cd2d2 --- /dev/null +++ b/utils/osd_login/osd_login @@ -0,0 +1,118 @@ +#!/bin/bash +# +# osd_login : This script is part of the autologin feature +# mandated by the pnfs-objects standard. +# It is called from objlayoutdriver.ko in the kernel. + +# Copyright (C) 2012, Sachin Bhamare +# Copyright (C) 2012, Boaz Harrosh +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA + +umask 022 + +PATH="/sbin:/usr/sbin:/bin:/usr/bin" + +iscsiadm=/sbin/iscsiadm + +PARENT_PID=$BASHPID +WATCHDOG_TIMEOUT=15 + +protocol="" +portal="" +uri="" +osdname="" +systemid="" + +usage() +{ + echo "Usage: $0 -u -o -s " + echo "Options:" + echo "-u target uri e.g. iscsi://:" + echo "-o osdname of the target OSD" + echo "-s systemid of the target OSD" +} + +parse_cmdline() +{ + argc=$# + if [ $# -lt 3 ]; then + usage + exit 1 + fi + + # parse the input arguments + while getopts "u:o:s:" options; do + case $options in + u ) uri=$OPTARG;; + o ) osdname=$OPTARG;; + s ) systemid=$OPTARG;; + \? ) usage + exit 1;; + * ) usage + exit 1;; + esac + done + + echo "-u : $uri" + echo "-o : $osdname" + echo "-s : $systemid" + + protocol=`echo $uri | awk -F ':' '{print $1}'` + portal=`echo $uri | awk -F '//' '{print $2}'` +} + +watchdog() +{ + timeout=$1 + portal=$2 + + sleep $timeout + if kill -9 $PARENT_PID; then + echo "watchdog : Timed out (>$timeout seconds) while login into $portal" | logger -t "osd_login" + fi + echo "watchdog: exiting .." + exit 2 +} + +login_iscsi_osd() +{ + echo "login into: $1" + if ! $iscsiadm -m discovery -o nonpersistent -t sendtargets -p $1 --login; then + echo "$iscsiadm -m discovery -t sendtargets -p $1 --login returned error $? !" + sleep 1; + fi +} + +echo "============= osd_login =========" +echo "progname : $0" +parse_cmdline "$@" +echo "protocol: $protocol" +echo "portal: $portal" + +watchdog $WATCHDOG_TIMEOUT $portal & +watchdog_pid=$! + +case $protocol in +iscsi) + login_iscsi_osd $portal |& logger -t "osd_login" + ;; +*) + echo "Error: protocol $protocol not supported !" | logger -t "osd_login" + ;; +esac + +kill -9 $watchdog_pid +exit 0 diff --git a/utils/showmount/Makefile.am b/utils/showmount/Makefile.am index 077b2c7..4ba5ead 100644 --- a/utils/showmount/Makefile.am +++ b/utils/showmount/Makefile.am @@ -7,7 +7,8 @@ sbin_PROGRAMS = showmount showmount_SOURCES = showmount.c showmount_LDADD = ../../support/export/libexport.a \ ../../support/nfs/libnfs.a \ - ../../support/misc/libmisc.a + ../../support/misc/libmisc.a \ + $(LIBTIRPC) showmount_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS) \ -I$(top_builddir)/support/export diff --git a/utils/statd/Makefile.am b/utils/statd/Makefile.am index 1744791..dc2bfc4 100644 --- a/utils/statd/Makefile.am +++ b/utils/statd/Makefile.am @@ -15,10 +15,10 @@ BUILT_SOURCES = $(GENFILES) statd_LDADD = ../../support/nsm/libnsm.a \ ../../support/nfs/libnfs.a \ ../../support/misc/libmisc.a \ - $(LIBWRAP) $(LIBNSL) $(LIBCAP) + $(LIBWRAP) $(LIBNSL) $(LIBCAP) $(LIBTIRPC) sm_notify_LDADD = ../../support/nsm/libnsm.a \ ../../support/nfs/libnfs.a \ - $(LIBNSL) $(LIBCAP) + $(LIBNSL) $(LIBCAP) $(LIBTIRPC) EXTRA_DIST = sim_sm_inter.x $(man8_MANS) COPYRIGHT simulate.c