Index: sys/fs/nfs/nfs.h =================================================================== --- sys/fs/nfs/nfs.h +++ sys/fs/nfs/nfs.h @@ -252,6 +252,11 @@ int nid_namelen; /* and its length */ }; +struct nfsuserd_args { + sa_family_t nuserd_family; /* Address family to use */ + u_short nuserd_port; /* Port# */ +}; + struct nfsd_clid { int nclid_idlen; /* Length of client id */ u_char nclid_id[NFSV4_OPAQUELIMIT]; /* and name */ Index: sys/fs/nfs/nfs_commonport.c =================================================================== --- sys/fs/nfs/nfs_commonport.c +++ sys/fs/nfs/nfs_commonport.c @@ -631,14 +631,24 @@ goto out; } else if (uap->flag & NFSSVC_NFSUSERDPORT) { u_short sockport; + struct nfsuserd_args nargs; - if ((uap->flag & NFSSVC_NEWSTRUCT) == 0) + if ((uap->flag & NFSSVC_NEWSTRUCT) == 0) { error = copyin(uap->argp, (caddr_t)&sockport, sizeof (u_short)); - else - error = ENXIO; + if (error == 0) { + nargs.nuserd_family = AF_INET; + nargs.nuserd_port = sockport; + } + } else { + /* + * New nfsuserd_args structure, which indicates + * which IP version to use along with the port#. + */ + error = copyin(uap->argp, &nargs, sizeof(nargs)); + } if (!error) - error = nfsrv_nfsuserdport(sockport, p); + error = nfsrv_nfsuserdport(&nargs, p); } else if (uap->flag & NFSSVC_NFSUSERDDELPORT) { nfsrv_nfsuserddelport(); error = 0; Index: sys/fs/nfs/nfs_commonsubs.c =================================================================== --- sys/fs/nfs/nfs_commonsubs.c +++ sys/fs/nfs/nfs_commonsubs.c @@ -42,6 +42,7 @@ * copy data between mbuf chains and uio lists. */ #ifndef APPLEKEXT +#include "opt_inet.h" #include "opt_inet6.h" #include @@ -3504,10 +3505,16 @@ * Set the port for the nfsuserd. */ APPLESTATIC int -nfsrv_nfsuserdport(u_short port, NFSPROC_T *p) +nfsrv_nfsuserdport(struct nfsuserd_args *nargs, NFSPROC_T *p) { struct nfssockreq *rp; +#ifdef INET struct sockaddr_in *ad; +#endif +#ifdef INET6 + struct sockaddr_in6 *ad6; + const struct in6_addr in6loopback = IN6ADDR_LOOPBACK_INIT; +#endif int error; NFSLOCKNAMEID(); @@ -3527,15 +3534,37 @@ rp->nr_soproto = IPPROTO_UDP; rp->nr_lock = (NFSR_RESERVEDPORT | NFSR_LOCALHOST); rp->nr_cred = NULL; - rp->nr_nam = malloc(sizeof(*rp->nr_nam), M_SONAME, M_WAITOK | M_ZERO); - NFSSOCKADDRSIZE(rp->nr_nam, sizeof (struct sockaddr_in)); - ad = NFSSOCKADDR(rp->nr_nam, struct sockaddr_in *); - ad->sin_family = AF_INET; - ad->sin_addr.s_addr = htonl((u_int32_t)0x7f000001); /* 127.0.0.1 */ - ad->sin_port = port; rp->nr_prog = RPCPROG_NFSUSERD; + error = 0; + switch (nargs->nuserd_family) { +#ifdef INET + case AF_INET: + rp->nr_nam = malloc(sizeof(struct sockaddr_in), M_SONAME, + M_WAITOK | M_ZERO); + ad = (struct sockaddr_in *)rp->nr_nam; + ad->sin_len = sizeof(struct sockaddr_in); + ad->sin_family = AF_INET; + ad->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + ad->sin_port = nargs->nuserd_port; + break; +#endif +#ifdef INET6 + case AF_INET6: + rp->nr_nam = malloc(sizeof(struct sockaddr_in6), M_SONAME, + M_WAITOK | M_ZERO); + ad6 = (struct sockaddr_in6 *)rp->nr_nam; + ad6->sin6_len = sizeof(struct sockaddr_in6); + ad6->sin6_family = AF_INET6; + ad6->sin6_addr = in6loopback; + ad6->sin6_port = nargs->nuserd_port; + break; +#endif + default: + error = ENXIO; + } rp->nr_vers = RPCNFSUSERD_VERS; - error = newnfs_connect(NULL, rp, NFSPROCCRED(p), p, 0); + if (error == 0) + error = newnfs_connect(NULL, rp, NFSPROCCRED(p), p, 0); if (error) { free(rp->nr_nam, M_SONAME); nfsrv_nfsuserd = 0; Index: sys/fs/nfs/nfs_var.h =================================================================== --- sys/fs/nfs/nfs_var.h +++ sys/fs/nfs/nfs_var.h @@ -136,7 +136,7 @@ NFSPROC_T *); int nfsrv_checkgetattr(struct nfsrv_descript *, vnode_t, struct nfsvattr *, nfsattrbit_t *, NFSPROC_T *); -int nfsrv_nfsuserdport(u_short, NFSPROC_T *); +int nfsrv_nfsuserdport(struct nfsuserd_args *, NFSPROC_T *); void nfsrv_nfsuserddelport(void); void nfsrv_throwawayallstate(NFSPROC_T *); int nfsrv_checksequence(struct nfsrv_descript *, uint32_t, uint32_t *, Index: usr.sbin/nfsuserd/Makefile =================================================================== --- usr.sbin/nfsuserd/Makefile +++ usr.sbin/nfsuserd/Makefile @@ -1,7 +1,16 @@ # $FreeBSD: head/usr.sbin/nfsuserd/Makefile 193070 2009-05-29 22:19:45Z delphij $ +.include + PROG= nfsuserd MAN= nfsuserd.8 WARNS?= 3 + +.if ${MK_INET_SUPPORT} != "no" +CFLAGS+= -DINET +.endif +.if ${MK_INET6_SUPPORT} != "no" +CFLAGS+= -DINET6 +.endif .include Index: usr.sbin/nfsuserd/nfsuserd.c =================================================================== --- usr.sbin/nfsuserd/nfsuserd.c +++ usr.sbin/nfsuserd/nfsuserd.c @@ -40,6 +40,10 @@ #include #include +#include + +#include + #include #include @@ -72,6 +76,7 @@ static bool_t xdr_getid(XDR *, caddr_t); static bool_t xdr_getname(XDR *, caddr_t); static bool_t xdr_retval(XDR *, caddr_t); +static int nfsbind_localhost(void); #define MAXNAME 1024 #define MAXNFSUSERD 20 @@ -94,6 +99,10 @@ int verbose = 0, im_a_slave = 0, nfsuserdcnt = -1, forcestart = 0; int defusertimeout = DEFUSERTIMEOUT, manage_gids = 0; pid_t slaves[MAXNFSUSERD]; +static struct sockaddr_storage fromip; +#ifdef INET6 +static struct in6_addr in6loopback = IN6ADDR_LOOPBACK_INIT; +#endif int main(int argc, char *argv[]) @@ -105,13 +114,20 @@ struct group *grp; int sock, one = 1; SVCXPRT *udptransp; - u_short portnum; + struct nfsuserd_args nargs; sigset_t signew; char hostname[MAXHOSTNAMELEN + 1], *cp; struct addrinfo *aip, hints; static uid_t check_dups[MAXUSERMAX]; gid_t grps[NGROUPS]; int ngroup; +#ifdef INET + struct sockaddr_in *sin; +#endif +#ifdef INET6 + struct sockaddr_in6 *sin6; +#endif + int s; if (modfind("nfscommon") < 0) { /* Not present in kernel, try loading it */ @@ -144,6 +160,37 @@ } } } + + /* + * See if this server handles IPv4 or IPv6 and set up the default + * localhost address. + */ + s = -1; +#ifdef INET6 + s = socket(PF_INET6, SOCK_DGRAM, 0); + if (s >= 0) { + fromip.ss_family = AF_INET6; + fromip.ss_len = sizeof(struct sockaddr_in6); + sin6 = (struct sockaddr_in6 *)&fromip; + sin6->sin6_addr = in6loopback; + close(s); + } +#endif /* INET6 */ +#ifdef INET + if (s < 0) { + s = socket(PF_INET, SOCK_DGRAM, 0); + if (s >= 0) { + fromip.ss_family = AF_INET; + fromip.ss_len = sizeof(struct sockaddr_in); + sin = (struct sockaddr_in *)&fromip; + sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + close(s); + } + } +#endif /* INET */ + if (s < 0) + err(1, "Can't create a inet/inet6 socket"); + nid.nid_usermax = DEFUSERMAX; nid.nid_usertimeout = defusertimeout; @@ -245,11 +292,12 @@ for (i = 0; i < nfsuserdcnt; i++) slaves[i] = (pid_t)-1; + nargs.nuserd_family = fromip.ss_family; /* * Set up the service port to accept requests via UDP from - * localhost (127.0.0.1). + * localhost (INADDR_LOOPBACK or IN6ADDR_LOOPBACK_INIT). */ - if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) + if ((sock = socket(nargs.nuserd_family, SOCK_DGRAM, IPPROTO_UDP)) < 0) err(1, "cannot create udp socket"); /* @@ -272,11 +320,11 @@ /* * Tell the kernel what my port# is. */ - portnum = htons(udptransp->xp_port); + nargs.nuserd_port = htons(udptransp->xp_port); #ifdef DEBUG - printf("portnum=0x%x\n", portnum); + printf("portnum=0x%x\n", nargs.nuserd_port); #else - if (nfssvc(NFSSVC_NFSUSERDPORT, (caddr_t)&portnum) < 0) { + if (nfssvc(NFSSVC_NFSUSERDPORT | NFSSVC_NEWSTRUCT, &nargs) < 0) { if (errno == EPERM) { fprintf(stderr, "Can't start nfsuserd when already running"); @@ -457,27 +505,92 @@ struct passwd *pwd; struct group *grp; int error; +#if defined(INET) || defined(INET6) u_short sport; + int ret; +#endif struct info info; struct nfsd_idargs nid; - u_int32_t saddr; gid_t grps[NGROUPS]; int ngroup; +#ifdef INET + struct sockaddr_in *fromsin, *sin; +#endif +#ifdef INET6 + struct sockaddr_in6 *fromsin6, *sin6; + char buf[INET6_ADDRSTRLEN]; +#endif /* - * Only handle requests from 127.0.0.1 on a reserved port number. + * Only handle requests from localhost on a reserved port number. + * If the upcall is from a different address, call nfsbind_localhost() + * to check for a remapping of localhost, due to jails. * (Since a reserved port # at localhost implies a client with * local root, there won't be a security breach. This is about * the only case I can think of where a reserved port # means * something.) */ - sport = ntohs(transp->xp_raddr.sin_port); - saddr = ntohl(transp->xp_raddr.sin_addr.s_addr); - if ((rqstp->rq_proc != NULLPROC && sport >= IPPORT_RESERVED) || - saddr != 0x7f000001) { - syslog(LOG_ERR, "req from ip=0x%x port=%d\n", saddr, sport); - svcerr_weakauth(transp); - return; + if (rqstp->rq_proc != NULLPROC) { + switch (fromip.ss_family) { +#ifdef INET + case AF_INET: + if (transp->xp_rtaddr.len < sizeof(*sin)) { + syslog(LOG_ERR, "xp_rtaddr too small"); + svcerr_weakauth(transp); + return; + } + sin = (struct sockaddr_in *)transp->xp_rtaddr.buf; + fromsin = (struct sockaddr_in *)&fromip; + sport = ntohs(sin->sin_port); + if (sport >= IPPORT_RESERVED) { + syslog(LOG_ERR, "not a reserved port#"); + svcerr_weakauth(transp); + return; + } + ret = 1; + if (sin->sin_addr.s_addr != fromsin->sin_addr.s_addr) + ret = nfsbind_localhost(); + if (ret == 0 || sin->sin_addr.s_addr != + fromsin->sin_addr.s_addr) { + syslog(LOG_ERR, "bad from ip %s", + inet_ntoa(sin->sin_addr)); + svcerr_weakauth(transp); + return; + } + break; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + if (transp->xp_rtaddr.len < sizeof(*sin6)) { + syslog(LOG_ERR, "xp_rtaddr too small"); + svcerr_weakauth(transp); + return; + } + sin6 = (struct sockaddr_in6 *)transp->xp_rtaddr.buf; + fromsin6 = (struct sockaddr_in6 *)&fromip; + sport = ntohs(sin6->sin6_port); + if (sport >= IPV6PORT_RESERVED) { + syslog(LOG_ERR, "not a reserved port#"); + svcerr_weakauth(transp); + return; + } + ret = 1; + if (!IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, + &fromsin6->sin6_addr)) + ret = nfsbind_localhost(); + if (ret == 0 || !IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, + &fromsin6->sin6_addr)) { + if (inet_ntop(AF_INET6, &sin6->sin6_addr, buf, + INET6_ADDRSTRLEN) != NULL) + syslog(LOG_ERR, "bad from ip %s", buf); + else + syslog(LOG_ERR, "bad from ip6 addr"); + svcerr_weakauth(transp); + return; + } + break; +#endif /* INET6 */ + } } switch (rqstp->rq_proc) { case NULLPROC: @@ -716,6 +829,67 @@ exit(1); } exit(0); +} + +/* + * Get the IP address that the localhost address maps to. + * This is needed when jails map localhost to another IP address. + */ +static int +nfsbind_localhost(void) +{ +#ifdef INET + struct sockaddr_in sin; +#endif +#ifdef INET6 + struct sockaddr_in6 sin6; +#endif + socklen_t slen; + int ret, s; + + switch (fromip.ss_family) { +#ifdef INET6 + case AF_INET6: + s = socket(PF_INET6, SOCK_DGRAM, 0); + if (s < 0) + return (0); + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_len = sizeof(sin6); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = in6loopback; + sin6.sin6_port = 0; + ret = bind(s, (struct sockaddr *)&sin6, sizeof(sin6)); + if (ret < 0) { + close(s); + return (0); + } + break; +#endif /* INET6 */ +#ifdef INET + case AF_INET: + s = socket(PF_INET, SOCK_DGRAM, 0); + if (s < 0) + return (0); + memset(&sin, 0, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = 0; + ret = bind(s, (struct sockaddr *)&sin, sizeof(sin)); + if (ret < 0) { + close(s); + return (0); + } + break; +#endif /* INET */ + } + memset(&fromip, 0, sizeof(fromip)); + slen = sizeof(fromip); + ret = getsockname(s, (struct sockaddr *)&fromip, &slen); + close(s); + if (ret < 0) + return (0); + return (1); } static void