# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2015 Nexenta Systems, Inc. All rights reserved. # Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. # # Copyright (c) 2018, Joyent, Inc. # Copyright 2022 RackTop Systems. FSTYPE = nfs TYPEPROG = mountd ATTMK = $(TYPEPROG) include ../../Makefile.fstype COMMON = nfs_sec.o sharetab.o daemon.o smfcfg.o LOCAL = mountd.o netgroup.o rmtab.o nfsauth.o \ nfsauth_xdr.o exportlist.o hashset.o nfs_cmd.o OBJS = $(FSLIB) $(LOCAL) $(COMMON) SRCS = $(LOCAL:%.o=%.c) $(FSLIBSRC) ../lib/nfs_sec.c \ ../lib/sharetab.c ../lib/daemon.c ../lib/smfcfg.c DSRC = mountd_dt.d DOBJ = $(DSRC:%.d=%.o) LDLIBS += -lrpcsvc -lnsl -lbsm -lsocket -linetutil -ltsnet -ltsol LDLIBS += -lnvpair -lscf -lumem CPPFLAGS += -D_REENTRANT -I../lib CERRWARN += $(CNOWARN_UNINIT) # unreachable code in mountd.c is to please the C compiler. mountd.o : SMOFF += check_unreachable $(TYPEPROG): $(OBJS) $(COMPILE.d) -s $(DSRC) -o $(DOBJ) $(OBJS) $(LINK.c) -o $@ $(DOBJ) $(OBJS) $(LDLIBS) $(POST_PROCESS) nfs_sec.o: ../lib/nfs_sec.c $(COMPILE.c) ../lib/nfs_sec.c sharetab.o: ../lib/sharetab.c $(COMPILE.c) ../lib/sharetab.c daemon.o: ../lib/daemon.c $(COMPILE.c) ../lib/daemon.c smfcfg.o: ../lib/smfcfg.c $(COMPILE.c) ../lib/smfcfg.c clean: $(RM) $(OBJS) $(DOBJ) /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../lib/sharetab.h" #include "mountd.h" static void freeexports(struct exportnode *); static struct groupnode **newgroup(char *, struct groupnode **); static struct exportnode **newexport(char *, struct groupnode *, struct exportnode **); static char *optlist[] = { #define OPT_RO 0 SHOPT_RO, #define OPT_RW 1 SHOPT_RW, NULL }; /* * Send current export list to a client */ void export(struct svc_req *rqstp) { SVCXPRT *transp; struct exportnode *exportlist; struct exportnode **tail; struct groupnode *groups; struct groupnode **grtail; struct share *sh; struct sh_list *shp; char *gr, *p, *opts, *val, *lasts; int export_to_everyone; transp = rqstp->rq_xprt; if (!svc_getargs(transp, xdr_void, NULL)) { svcerr_decode(transp); return; } check_sharetab(); exportlist = NULL; tail = &exportlist; (void) rw_rdlock(&sharetab_lock); for (shp = share_list; shp; shp = shp->shl_next) { groups = NULL; grtail = &groups; sh = shp->shl_sh; /* * Check for "ro" or "rw" list without argument values. This * indicates export to everyone. Unfortunately, SunOS 4.x * automounter uses this, and it is indicated indirectly with * 'showmount -e'. * * If export_to_everyone is 1, then groups should be NULL to * indicate export to everyone. */ opts = strdup(sh->sh_opts); p = opts; export_to_everyone = 0; while (*p) { switch (getsubopt(&p, optlist, &val)) { case OPT_RO: case OPT_RW: if (val == NULL) export_to_everyone = 1; break; } } free(opts); if (export_to_everyone == 0) { opts = strdup(sh->sh_opts); p = opts; /* * Just concatenate all the hostnames/groups * from the "ro" and "rw" lists for each flavor. * This list is rather meaningless now, but * that's what the protocol demands. */ while (*p) { switch (getsubopt(&p, optlist, &val)) { case OPT_RO: case OPT_RW: while ((gr = strtok_r(val, ":", &lasts)) != NULL) { val = NULL; grtail = newgroup(gr, grtail); } break; } } free(opts); } tail = newexport(sh->sh_path, groups, tail); } (void) rw_unlock(&sharetab_lock); errno = 0; if (!svc_sendreply(transp, xdr_exports, (char *)&exportlist)) log_cant_reply(transp); freeexports(exportlist); } static void freeexports(struct exportnode *ex) { struct groupnode *groups, *tmpgroups; struct exportnode *tmpex; while (ex) { groups = ex->ex_groups; while (groups) { tmpgroups = groups->gr_next; free(groups->gr_name); free(groups); groups = tmpgroups; } tmpex = ex->ex_next; free(ex->ex_dir); free(ex); ex = tmpex; } } static struct groupnode ** newgroup(char *grname, struct groupnode **tail) { struct groupnode *new; char *newname; new = exmalloc(sizeof (*new)); newname = exmalloc(strlen(grname) + 1); (void) strcpy(newname, grname); new->gr_name = newname; new->gr_next = NULL; *tail = new; return (&new->gr_next); } static struct exportnode ** newexport(char *grname, struct groupnode *grplist, struct exportnode **tail) { struct exportnode *new; char *newname; new = exmalloc(sizeof (*new)); newname = exmalloc(strlen(grname) + 1); (void) strcpy(newname, grname); new->ex_dir = newname; new->ex_groups = grplist; new->ex_next = NULL; *tail = new; return (&new->ex_next); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include "hashset.h" #include "mountd.h" #include /* * HASHSET is hash table managing pointers to a set of keys * (set is a collection without duplicates). The public interface * of the HASHSET is similar to the java.util.Set interface. * Unlike the libc `hsearch' based hash table, this implementation * does allow multiple instances of HASHSET within a single application, * and the HASHSET_ITERATOR allows to iterate through the entire set * using h_next(). * * HASHSET does not store actual keys but only pointers to keys. Hence the * data remains intact when HASHSET grows (resizes itself). HASHSET accesses * the actual key data only through the hash and equal functions given * as arguments to h_create. * * Hash collisions are resolved with linked lists. */ typedef struct HashSetEntry { uint_t e_hash; /* Hash value */ const void *e_key; /* Pointer to a key */ struct HashSetEntry *e_next; } ENTRY; struct HashSet { ENTRY **h_table; /* Pointer to an array of ENTRY'ies */ uint_t h_tableSize; /* Size of the array */ uint_t h_count; /* Current count */ uint_t h_threshold; /* loadFactor threshold */ float h_loadFactor; /* Current loadFactor (h_count/h_tableSize( */ uint_t (*h_hash) (const void *); int (*h_equal) (const void *, const void *); }; struct HashSetIterator { HASHSET i_h; uint_t i_indx; ENTRY *i_e; uint_t i_coll; }; static void rehash(HASHSET h); #define DEFAULT_INITIALCAPACITY 1 #define DEFAULT_LOADFACTOR 0.75 /* * Create a HASHSET * - HASHSET is a hash table of pointers to keys * - duplicate keys are not allowed * - the HASHSET is opaque and can be accessed only through the h_ functions * - two keys k1 and k2 are considered equal if the result of equal(k1, k2) * is non-zero * - the function hash(key) is used to compute hash values for keys; if * keys are "equal" the values returned by the hash function must be * identical. */ HASHSET h_create(uint_t (*hash) (const void *), int (*equal) (const void *, const void *), uint_t initialCapacity, float loadFactor) { HASHSET h; if (initialCapacity == 0) initialCapacity = DEFAULT_INITIALCAPACITY; if (loadFactor < 0.0) loadFactor = DEFAULT_LOADFACTOR; h = exmalloc(sizeof (*h)); if (h == NULL) return (NULL); h->h_table = exmalloc(initialCapacity * sizeof (ENTRY *)); (void) memset(h->h_table, 0, initialCapacity * sizeof (ENTRY *)); if (h->h_table == NULL) { free(h); return (NULL); } h->h_tableSize = initialCapacity; h->h_hash = hash; h->h_equal = equal; h->h_loadFactor = loadFactor; h->h_threshold = (uint_t)(initialCapacity * loadFactor); h->h_count = 0; return (h); } /* * Return a pointer to a matching key */ const void * h_get(const HASHSET h, void *key) { uint_t hash = h->h_hash(key); uint_t i = hash % h->h_tableSize; ENTRY *e; for (e = h->h_table[i]; e; e = e->e_next) if (e->e_hash == hash && h->h_equal(e->e_key, key)) return (e->e_key); return (NULL); } /* * Rehash (grow) HASHSET * - called when loadFactor exceeds threshold * - new size is 2*old_size+1 */ static void rehash(HASHSET h) { uint_t i = h->h_tableSize; uint_t newtabSize = 2 * i + 1; ENTRY **newtab = exmalloc(newtabSize * sizeof (ENTRY *)); (void) memset(newtab, 0, newtabSize * sizeof (ENTRY *)); while (i--) { ENTRY *e, *next; for (e = h->h_table[i]; e; e = next) { uint_t k = e->e_hash % newtabSize; next = (ENTRY *) e->e_next; e->e_next = (ENTRY *) newtab[k]; newtab[k] = e; } } h->h_threshold = (uint_t)(newtabSize * h->h_loadFactor); h->h_tableSize = newtabSize; free(h->h_table); h->h_table = newtab; } /* * Store a key into a HASHSET * - if there is already an "equal" key then the new key will not * be stored and the function returns a pointer to an existing key * - otherwise the function stores pointer to the new key and return NULL */ const void * h_put(HASHSET h, const void *key) { uint_t hash = h->h_hash(key); uint_t indx = hash % h->h_tableSize; ENTRY *e; for (e = h->h_table[indx]; e; e = e->e_next) if (e->e_hash == hash && h->h_equal(e->e_key, key)) return (key); if (h->h_count >= h->h_threshold) { rehash(h); indx = hash % h->h_tableSize; } e = exmalloc(sizeof (ENTRY)); e->e_hash = hash; e->e_key = (void *) key; e->e_next = h->h_table[indx]; h->h_table[indx] = e; h->h_count++; DTRACE_PROBE2(mountd, hashset, h->h_count, h->h_loadFactor); return (NULL); } /* * Delete a key * - if there is no "equal" key in the HASHSET the fuction returns NULL * - otherwise the function returns a pointer to the deleted key */ const void * h_delete(HASHSET h, const void *key) { uint_t hash = h->h_hash(key); uint_t indx = hash % h->h_tableSize; ENTRY *e, *prev; for (e = h->h_table[indx], prev = NULL; e; prev = e, e = e->e_next) { if (e->e_hash == hash && h->h_equal(e->e_key, key)) { key = e->e_key; if (prev) prev->e_next = e->e_next; else h->h_table[indx] = e->e_next; free(e); return (key); } } return (NULL); } /* * Return an opaque HASHSET_ITERATOR (to be used in h_next()) */ HASHSET_ITERATOR h_iterator(HASHSET h) { HASHSET_ITERATOR i = exmalloc(sizeof (*i)); i->i_h = h; i->i_indx = h->h_tableSize; i->i_e = NULL; i->i_coll = 0; return (i); } /* * Return a pointer to a next key */ const void * h_next(HASHSET_ITERATOR i) { const void *key; while (i->i_e == NULL) { if (i->i_indx-- == 0) return (NULL); i->i_e = i->i_h->h_table[i->i_indx]; } key = i->i_e->e_key; i->i_e = i->i_e->e_next; if (i->i_e) i->i_coll++; return (key); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1999, by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _HASHSET_H #define _HASHSET_H #include #ifdef __cplusplus extern "C" { #endif typedef struct HashSet *HASHSET; typedef struct HashSetIterator *HASHSET_ITERATOR; extern HASHSET h_create(uint_t (*hash) (const void *), int (*equal) (const void *, const void *), uint_t initialCapacity, float loadFactor); extern const void *h_get(const HASHSET h, void *key); extern const void *h_put(HASHSET h, const void *key); extern const void *h_delete(HASHSET h, const void *key); extern HASHSET_ITERATOR h_iterator(HASHSET h); extern const void *h_next(HASHSET_ITERATOR i); #ifdef __cplusplus } #endif #endif /* _HASHSET_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2016 by Delphix. All rights reserved. * Copyright 2016 Nexenta Systems, Inc. All rights reserved. * Copyright 2022 RackTop Systems. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../fslib.h" #include #include #include "../lib/sharetab.h" #include "mountd.h" #include #include #include #include #include #include #include #include #include "smfcfg.h" #include #include #include #include #include extern int daemonize_init(void); extern void daemonize_fini(int); extern int _nfssys(int, void *); struct sh_list *share_list; rwlock_t sharetab_lock; /* lock to protect the cached sharetab */ static mutex_t mnttab_lock; /* prevent concurrent mnttab readers */ static mutex_t logging_queue_lock; static cond_t logging_queue_cv; static share_t *find_lofsentry(char *, int *); static int getclientsflavors_old(share_t *, struct cln *, int *); static int getclientsflavors_new(share_t *, struct cln *, int *); static int check_client_old(share_t *, struct cln *, int, uid_t, gid_t, uint_t, gid_t *, uid_t *, gid_t *, uint_t *, gid_t **); static int check_client_new(share_t *, struct cln *, int, uid_t, gid_t, uint_t, gid_t *, uid_t *, gid_t *i, uint_t *, gid_t **); static void mnt(struct svc_req *, SVCXPRT *); static void mnt_pathconf(struct svc_req *); static int mount(struct svc_req *r); static void sh_free(struct sh_list *); static void umount(struct svc_req *); static void umountall(struct svc_req *); static int newopts(char *); static tsol_tpent_t *get_client_template(struct sockaddr *); static int debug; static int verbose; static int rejecting; static int mount_vers_min = MOUNTVERS; static int mount_vers_max = MOUNTVERS3; static int mountd_port = 0; static boolean_t mountd_remote_dump = B_FALSE; extern void nfscmd_func(void *, char *, size_t, door_desc_t *, uint_t); thread_t nfsauth_thread; thread_t cmd_thread; thread_t logging_thread; typedef struct logging_data { char *ld_host; char *ld_path; char *ld_rpath; int ld_status; char *ld_netid; struct netbuf *ld_nb; struct logging_data *ld_next; } logging_data; static logging_data *logging_head = NULL; static logging_data *logging_tail = NULL; /* * Our copy of some system variables obtained using sysconf(3c) */ static long ngroups_max; /* _SC_NGROUPS_MAX */ static long pw_size; /* _SC_GETPW_R_SIZE_MAX */ /* Cached address info for this host. */ static struct addrinfo *host_ai = NULL; static void * nfsauth_svc(void *arg __unused) { int doorfd = -1; uint_t darg; #ifdef DEBUG int dfd; #endif if ((doorfd = door_create(nfsauth_func, NULL, DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) { syslog(LOG_ERR, "Unable to create door: %m\n"); exit(10); } #ifdef DEBUG /* * Create a file system path for the door */ if ((dfd = open(MOUNTD_DOOR, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1) { syslog(LOG_ERR, "Unable to open %s: %m\n", MOUNTD_DOOR); (void) close(doorfd); exit(11); } /* * Clean up any stale namespace associations */ (void) fdetach(MOUNTD_DOOR); /* * Register in namespace to pass to the kernel to door_ki_open */ if (fattach(doorfd, MOUNTD_DOOR) == -1) { syslog(LOG_ERR, "Unable to fattach door: %m\n"); (void) close(dfd); (void) close(doorfd); exit(12); } (void) close(dfd); #endif /* * Must pass the doorfd down to the kernel. */ darg = doorfd; (void) _nfssys(MOUNTD_ARGS, &darg); /* * Wait for incoming calls */ /*CONSTCOND*/ for (;;) (void) pause(); /*NOTREACHED*/ syslog(LOG_ERR, gettext("Door server exited")); return (NULL); } /* * NFS command service thread code for setup and handling of the * nfs_cmd requests for character set conversion and other future * events. */ static void * cmd_svc(void *arg) { int doorfd = -1; uint_t darg; if ((doorfd = door_create(nfscmd_func, NULL, DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) { syslog(LOG_ERR, "Unable to create cmd door: %m\n"); exit(10); } /* * Must pass the doorfd down to the kernel. */ darg = doorfd; (void) _nfssys(NFSCMD_ARGS, &darg); /* * Wait for incoming calls */ /*CONSTCOND*/ for (;;) (void) pause(); /*NOTREACHED*/ syslog(LOG_ERR, gettext("Cmd door server exited")); return (NULL); } static void free_logging_data(logging_data *lq) { if (lq != NULL) { free(lq->ld_host); free(lq->ld_netid); if (lq->ld_nb != NULL) { free(lq->ld_nb->buf); free(lq->ld_nb); } free(lq->ld_path); free(lq->ld_rpath); free(lq); } } static logging_data * remove_head_of_queue(void) { logging_data *lq; /* * Pull it off the queue. */ lq = logging_head; if (lq) { logging_head = lq->ld_next; /* * Drained it. */ if (logging_head == NULL) { logging_tail = NULL; } } return (lq); } static void do_logging_queue(logging_data *lq) { int cleared = 0; char *host; while (lq) { struct cln cln; if (lq->ld_host == NULL) { DTRACE_PROBE(mountd, name_by_lazy); cln_init_lazy(&cln, lq->ld_netid, lq->ld_nb); host = cln_gethost(&cln); } else host = lq->ld_host; audit_mountd_mount(host, lq->ld_path, lq->ld_status); /* BSM */ /* add entry to mount list */ if (lq->ld_rpath) mntlist_new(host, lq->ld_rpath); if (lq->ld_host == NULL) cln_fini(&cln); free_logging_data(lq); cleared++; (void) mutex_lock(&logging_queue_lock); lq = remove_head_of_queue(); (void) mutex_unlock(&logging_queue_lock); } DTRACE_PROBE1(mountd, logging_cleared, cleared); } static void * logging_svc(void *arg __unused) { logging_data *lq; for (;;) { (void) mutex_lock(&logging_queue_lock); while (logging_head == NULL) { (void) cond_wait(&logging_queue_cv, &logging_queue_lock); } lq = remove_head_of_queue(); (void) mutex_unlock(&logging_queue_lock); do_logging_queue(lq); } /*NOTREACHED*/ syslog(LOG_ERR, gettext("Logging server exited")); return (NULL); } /* * This function is called for each configured network type to * bind and register our RPC service programs. * * On TCP or UDP, we may want to bind MOUNTPROG on a specific port * (when mountd_port is specified) in which case we'll use the * variant of svc_tp_create() that lets us pass a bind address. */ static void md_svc_tp_create(struct netconfig *nconf) { char port_str[8]; struct nd_hostserv hs; struct nd_addrlist *al = NULL; SVCXPRT *xprt = NULL; rpcvers_t vers; vers = mount_vers_max; /* * If mountd_port is set and this is an inet transport, * bind this service on the specified port. The TLI way * to create such a bind address is netdir_getbyname() * with the special "host" HOST_SELF_BIND. This builds * an all-zeros IP address with the specified port. */ if (mountd_port != 0 && (strcmp(nconf->nc_protofmly, NC_INET) == 0 || strcmp(nconf->nc_protofmly, NC_INET6) == 0)) { int err; (void) snprintf(port_str, sizeof (port_str), "%u", (unsigned short)mountd_port); hs.h_host = HOST_SELF_BIND; hs.h_serv = port_str; err = netdir_getbyname((struct netconfig *)nconf, &hs, &al); if (err == 0 && al != NULL) { xprt = svc_tp_create_addr(mnt, MOUNTPROG, vers, nconf, al->n_addrs); netdir_free(al, ND_ADDRLIST); } if (xprt == NULL) { syslog(LOG_ERR, "mountd: unable to create " "(MOUNTD,%d) on transport %s (port %d)", vers, nconf->nc_netid, mountd_port); } /* fall-back to default bind */ } if (xprt == NULL) { /* * Had mountd_port=0, or non-inet transport, * or the bind to a specific port failed. * Do a default bind. */ xprt = svc_tp_create(mnt, MOUNTPROG, vers, nconf); } if (xprt == NULL) { syslog(LOG_ERR, "mountd: unable to create " "(MOUNTD,%d) on transport %s", vers, nconf->nc_netid); return; } /* * Register additional versions on this transport. */ while (--vers >= mount_vers_min) { if (!svc_reg(xprt, MOUNTPROG, vers, mnt, nconf)) { (void) syslog(LOG_ERR, "mountd: " "failed to register vers %d on %s", vers, nconf->nc_netid); } } } int main(int argc, char *argv[]) { int pid; int c; int rpc_svc_fdunlim = 1; int rpc_svc_mode = RPC_SVC_MT_AUTO; int maxrecsz = RPC_MAXDATASIZE; bool_t exclbind = TRUE; bool_t can_do_mlp; long thr_flags = (THR_NEW_LWP|THR_DAEMON); char defval[5]; int ret, bufsz; struct rlimit rl; int listen_backlog = 0; int max_threads = 0; int tmp; struct netconfig *nconf; NCONF_HANDLE *nc; const char *errstr; int pipe_fd = -1; char hostbuf[256]; /* * Mountd requires uid 0 for: * /etc/rmtab updates (we could chown it to daemon) * /etc/dfs/dfstab reading (it wants to lock out share which * doesn't do any locking before first truncate; * NFS share does; should use fcntl locking instead) * Needed privileges: * auditing * nfs syscall * file dac search (so it can stat all files) * Optional privileges: * MLP */ can_do_mlp = priv_ineffect(PRIV_NET_BINDMLP); if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET, -1, -1, PRIV_SYS_NFS, PRIV_PROC_AUDIT, PRIV_FILE_DAC_SEARCH, PRIV_NET_PRIVADDR, can_do_mlp ? PRIV_NET_BINDMLP : NULL, NULL) == -1) { (void) fprintf(stderr, "%s: must be run with sufficient privileges\n", argv[0]); exit(1); } if (getrlimit(RLIMIT_NOFILE, &rl) != 0) { syslog(LOG_ERR, "getrlimit failed"); } else { rl.rlim_cur = rl.rlim_max; if (setrlimit(RLIMIT_NOFILE, &rl) != 0) syslog(LOG_ERR, "setrlimit failed"); } (void) enable_extended_FILE_stdio(-1, -1); /* Upgrade SMF settings, if necessary. */ nfs_config_upgrade(NFSD); ret = nfs_smf_get_iprop("mountd_max_threads", &max_threads, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, NFSD); if (ret != SA_OK) { syslog(LOG_ERR, "Reading of mountd_max_threads from SMF " "failed, using default value"); } ret = nfs_smf_get_iprop("mountd_port", &mountd_port, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, NFSD); if (ret != SA_OK) { syslog(LOG_ERR, "Reading of mountd_port from SMF " "failed, using default value"); } while ((c = getopt(argc, argv, "dvrm:p:")) != EOF) { switch (c) { case 'd': debug++; break; case 'v': verbose++; break; case 'r': rejecting = 1; break; case 'm': tmp = strtonum(optarg, 1, INT_MAX, &errstr); if (errstr != NULL) { (void) fprintf(stderr, "%s: invalid " "max_threads option: %s, using defaults\n", argv[0], errstr); break; } max_threads = tmp; break; case 'p': tmp = strtonum(optarg, 1, UINT16_MAX, &errstr); if (errstr != NULL) { (void) fprintf(stderr, "%s: invalid port " "number: %s\n", argv[0], errstr); break; } mountd_port = tmp; break; default: fprintf(stderr, "usage: mountd [-v] [-r]\n"); exit(1); } } /* * One might be tempted to use the NFS configuration values * server_versmin, server_versmax to constrain the range of * mountd versions supported here. However, older clients * use mountd V1 for showmount, so just leave all mountd * versions enabled here. (mount_vers_min, mount_vers_max) */ ret = nfs_smf_get_iprop("mountd_listen_backlog", &listen_backlog, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, NFSD); if (ret != SA_OK) { syslog(LOG_ERR, "Reading of mountd_listen_backlog from SMF " "failed, using default value"); } bufsz = sizeof (defval); ret = nfs_smf_get_prop("mountd_remote_dump", defval, DEFAULT_INSTANCE, SCF_TYPE_BOOLEAN, NFSD, &bufsz); if (ret == SA_OK) { mountd_remote_dump = string_to_boolean(defval); } if (!mountd_remote_dump) { /* Cache host address list */ if (gethostname(hostbuf, sizeof (hostbuf)) < 0) { syslog(LOG_ERR, "gethostname() failed"); exit(1); } if (getaddrinfo(hostbuf, NULL, NULL, &host_ai) != 0) { syslog(LOG_ERR, "getaddrinfo() failed"); exit(1); } } /* * Sanity check versions, * even though we may get versions > MOUNTVERS3, we still need * to start nfsauth service, so continue on regardless of values. */ if (mount_vers_max > MOUNTVERS3) mount_vers_max = MOUNTVERS3; if (mount_vers_min > mount_vers_max) { fprintf(stderr, "server_versmin > server_versmax\n"); mount_vers_max = mount_vers_min; } (void) setlocale(LC_ALL, ""); (void) rwlock_init(&sharetab_lock, USYNC_THREAD, NULL); (void) mutex_init(&mnttab_lock, USYNC_THREAD, NULL); (void) mutex_init(&logging_queue_lock, USYNC_THREAD, NULL); (void) cond_init(&logging_queue_cv, USYNC_THREAD, NULL); netgroup_init(); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); /* Don't drop core if the NFS module isn't loaded. */ (void) signal(SIGSYS, SIG_IGN); if (!debug) pipe_fd = daemonize_init(); /* * If we coredump it'll be in /core */ if (chdir("/") < 0) fprintf(stderr, "chdir /: %s\n", strerror(errno)); if (!debug) openlog("mountd", LOG_PID, LOG_DAEMON); /* * establish our lock on the lock file and write our pid to it. * exit if some other process holds the lock, or if there's any * error in writing/locking the file. */ pid = _enter_daemon_lock(MOUNTD); switch (pid) { case 0: break; case -1: fprintf(stderr, "error locking for %s: %s\n", MOUNTD, strerror(errno)); exit(2); default: /* daemon was already running */ exit(0); } audit_mountd_setup(); /* BSM */ /* * Get required system variables */ if ((ngroups_max = sysconf(_SC_NGROUPS_MAX)) == -1) { syslog(LOG_ERR, "Unable to get _SC_NGROUPS_MAX"); exit(1); } if ((pw_size = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1) { syslog(LOG_ERR, "Unable to get _SC_GETPW_R_SIZE_MAX"); exit(1); } /* * Set number of file descriptors to unlimited */ if (!rpc_control(RPC_SVC_USE_POLLFD, &rpc_svc_fdunlim)) { syslog(LOG_INFO, "unable to set number of FDs to unlimited"); } /* * Tell RPC that we want automatic thread mode. * A new thread will be spawned for each request. */ if (!rpc_control(RPC_SVC_MTMODE_SET, &rpc_svc_mode)) { fprintf(stderr, "unable to set automatic MT mode\n"); exit(1); } /* * Enable non-blocking mode and maximum record size checks for * connection oriented transports. */ if (!rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrecsz)) { fprintf(stderr, "unable to set RPC max record size\n"); } /* * Prevent our non-priv udp and tcp ports bound w/wildcard addr * from being hijacked by a bind to a more specific addr. */ if (!rpc_control(__RPC_SVC_EXCLBIND_SET, &exclbind)) { fprintf(stderr, "warning: unable to set udp/tcp EXCLBIND\n"); } /* * Set the maximum number of outstanding connection * indications (listen backlog) to the value specified. */ if (listen_backlog > 0 && !rpc_control(__RPC_SVC_LSTNBKLOG_SET, &listen_backlog)) { fprintf(stderr, "unable to set listen backlog\n"); exit(1); } /* * If max_threads was specified, then set the * maximum number of threads to the value specified. */ if (max_threads > 0 && !rpc_control(RPC_SVC_THRMAX_SET, &max_threads)) { fprintf(stderr, "unable to set max_threads\n"); exit(1); } if (mountd_port < 0 || mountd_port > UINT16_MAX) { fprintf(stderr, "unable to use specified port\n"); exit(1); } /* * Make sure to unregister any previous versions in case the * user is reconfiguring the server in interesting ways. */ svc_unreg(MOUNTPROG, MOUNTVERS); svc_unreg(MOUNTPROG, MOUNTVERS_POSIX); svc_unreg(MOUNTPROG, MOUNTVERS3); /* * Create the nfsauth thread with same signal disposition * as the main thread. We need to create a separate thread * since mountd() will be both an RPC server (for remote * traffic) _and_ a doors server (for kernel upcalls). */ if (thr_create(NULL, 0, nfsauth_svc, 0, thr_flags, &nfsauth_thread)) { fprintf(stderr, gettext("Failed to create NFSAUTH svc thread\n")); exit(2); } /* * Create the cmd service thread with same signal disposition * as the main thread. We need to create a separate thread * since mountd() will be both an RPC server (for remote * traffic) _and_ a doors server (for kernel upcalls). */ if (thr_create(NULL, 0, cmd_svc, 0, thr_flags, &cmd_thread)) { syslog(LOG_ERR, gettext("Failed to create CMD svc thread")); exit(2); } /* * Create an additional thread to service the rmtab and * audit_mountd_mount logging for mount requests. Use the same * signal disposition as the main thread. We create * a separate thread to allow the mount request threads to * clear as soon as possible. */ if (thr_create(NULL, 0, logging_svc, 0, thr_flags, &logging_thread)) { syslog(LOG_ERR, gettext("Failed to create LOGGING svc thread")); exit(2); } /* * Enumerate network transports and create service listeners * as appropriate for each. */ if ((nc = setnetconfig()) == NULL) { syslog(LOG_ERR, "setnetconfig failed: %m"); return (-1); } while ((nconf = getnetconfig(nc)) != NULL) { /* * Skip things like tpi_raw, invisible... */ if ((nconf->nc_flag & NC_VISIBLE) == 0) continue; if (nconf->nc_semantics != NC_TPI_CLTS && nconf->nc_semantics != NC_TPI_COTS && nconf->nc_semantics != NC_TPI_COTS_ORD) continue; md_svc_tp_create(nconf); } (void) endnetconfig(nc); /* * Start serving */ rmtab_load(); daemonize_fini(pipe_fd); /* Get rid of the most dangerous basic privileges. */ __fini_daemon_priv(PRIV_PROC_EXEC, PRIV_PROC_INFO, PRIV_PROC_SESSION, (char *)NULL); svc_run(); syslog(LOG_ERR, "Error: svc_run shouldn't have returned"); abort(); /* NOTREACHED */ return (0); } /* * copied from usr/src/uts/common/klm/nlm_impl.c */ static bool_t caller_is_local(SVCXPRT *transp) { struct addrinfo *a; char *netid; struct netbuf *rtaddr; struct sockaddr_storage addr; bool_t rv = FALSE; netid = transp->xp_netid; rtaddr = svc_getrpccaller(transp); if (netid == NULL) return (FALSE); if (strcmp(netid, "ticlts") == 0 || strcmp(netid, "ticotsord") == 0) return (TRUE); if (strcmp(netid, "tcp") == 0 || strcmp(netid, "udp") == 0) { struct sockaddr_in *sin = (void *)rtaddr->buf; if (sin->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) return (TRUE); memmove(&addr, sin, sizeof (*sin)); } if (strcmp(netid, "tcp6") == 0 || strcmp(netid, "udp6") == 0) { struct sockaddr_in6 *sin6 = (void *)rtaddr->buf; if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) return (TRUE); memmove(&addr, sin6, sizeof (*sin6)); } for (a = host_ai; a != NULL; a = a->ai_next) { if (sockaddrcmp(&addr, (struct sockaddr_storage *)a->ai_addr)) { rv = TRUE; break; } } return (rv); } /* * Server procedure switch routine */ void mnt(struct svc_req *rqstp, SVCXPRT *transp) { switch (rqstp->rq_proc) { case NULLPROC: errno = 0; if (!svc_sendreply(transp, xdr_void, (char *)0)) log_cant_reply(transp); return; case MOUNTPROC_MNT: (void) mount(rqstp); return; case MOUNTPROC_DUMP: if (mountd_remote_dump || caller_is_local(transp)) mntlist_send(transp); else svcerr_noproc(transp); return; case MOUNTPROC_UMNT: umount(rqstp); return; case MOUNTPROC_UMNTALL: umountall(rqstp); return; case MOUNTPROC_EXPORT: case MOUNTPROC_EXPORTALL: export(rqstp); return; case MOUNTPROC_PATHCONF: if (rqstp->rq_vers == MOUNTVERS_POSIX) mnt_pathconf(rqstp); else svcerr_noproc(transp); return; default: svcerr_noproc(transp); return; } } void log_cant_reply_cln(struct cln *cln) { int saverrno; char *host; saverrno = errno; /* save error code */ host = cln_gethost(cln); if (host == NULL) return; errno = saverrno; if (errno == 0) syslog(LOG_ERR, "couldn't send reply to %s", host); else syslog(LOG_ERR, "couldn't send reply to %s: %m", host); } void log_cant_reply(SVCXPRT *transp) { int saverrno; struct cln cln; saverrno = errno; /* save error code */ cln_init(&cln, transp); errno = saverrno; log_cant_reply_cln(&cln); cln_fini(&cln); } /* * Answer pathconf questions for the mount point fs */ static void mnt_pathconf(struct svc_req *rqstp) { SVCXPRT *transp; struct pathcnf p; char *path, rpath[MAXPATHLEN]; struct stat st; transp = rqstp->rq_xprt; path = NULL; (void) memset((caddr_t)&p, 0, sizeof (p)); if (!svc_getargs(transp, xdr_dirpath, (caddr_t)&path)) { svcerr_decode(transp); return; } if (lstat(path, &st) < 0) { _PC_SET(_PC_ERROR, p.pc_mask); goto done; } /* * Get a path without symbolic links. */ if (realpath(path, rpath) == NULL) { syslog(LOG_DEBUG, "mount request: realpath failed on %s: %m", path); _PC_SET(_PC_ERROR, p.pc_mask); goto done; } (void) memset((caddr_t)&p, 0, sizeof (p)); /* * can't ask about devices over NFS */ _PC_SET(_PC_MAX_CANON, p.pc_mask); _PC_SET(_PC_MAX_INPUT, p.pc_mask); _PC_SET(_PC_PIPE_BUF, p.pc_mask); _PC_SET(_PC_VDISABLE, p.pc_mask); errno = 0; p.pc_link_max = pathconf(rpath, _PC_LINK_MAX); if (errno) _PC_SET(_PC_LINK_MAX, p.pc_mask); p.pc_name_max = pathconf(rpath, _PC_NAME_MAX); if (errno) _PC_SET(_PC_NAME_MAX, p.pc_mask); p.pc_path_max = pathconf(rpath, _PC_PATH_MAX); if (errno) _PC_SET(_PC_PATH_MAX, p.pc_mask); if (pathconf(rpath, _PC_NO_TRUNC) == 1) _PC_SET(_PC_NO_TRUNC, p.pc_mask); if (pathconf(rpath, _PC_CHOWN_RESTRICTED) == 1) _PC_SET(_PC_CHOWN_RESTRICTED, p.pc_mask); done: errno = 0; if (!svc_sendreply(transp, xdr_ppathcnf, (char *)&p)) log_cant_reply(transp); if (path != NULL) (void) svc_freeargs(transp, xdr_dirpath, (caddr_t)&path); } /* * If the rootmount (export) option is specified, the all mount requests for * subdirectories return EACCES. */ static int checkrootmount(share_t *sh, char *rpath) { char *val; if ((val = getshareopt(sh->sh_opts, SHOPT_NOSUB)) != NULL) { free(val); if (strcmp(sh->sh_path, rpath) != 0) return (0); else return (1); } else return (1); } #define MAX_FLAVORS 128 /* * Return only EACCES if client does not have access * to this directory. * "If the server exports only /a/b, an attempt to * mount a/b/c will fail with ENOENT if the directory * does not exist"... However, if the client * does not have access to /a/b, an attacker can * determine whether the directory exists. * This routine checks either existence of the file or * existence of the file name entry in the mount table. * If the file exists and there is no file name entry, * the error returned should be EACCES. * If the file does not exist, it must be determined * whether the client has access to a parent * directory. If the client has access to a parent * directory, the error returned should be ENOENT, * otherwise EACCES. */ static int mount_enoent_error(struct cln *cln, char *path, char *rpath, int *flavor_list) { char *checkpath, *dp; share_t *sh = NULL; int realpath_error = ENOENT, reply_error = EACCES, lofs_tried = 0; int flavor_count; checkpath = strdup(path); if (checkpath == NULL) { syslog(LOG_ERR, "mount_enoent: no memory"); return (EACCES); } /* CONSTCOND */ while (1) { if (sh) { sharefree(sh); sh = NULL; } if ((sh = findentry(rpath)) == NULL && (sh = find_lofsentry(rpath, &lofs_tried)) == NULL) { /* * There is no file name entry. * If the file (with symbolic links resolved) exists, * the error returned should be EACCES. */ if (realpath_error == 0) break; } else if (checkrootmount(sh, rpath) == 0) { /* * This is a "nosub" only export, in which case, * mounting subdirectories isn't allowed. * If the file (with symbolic links resolved) exists, * the error returned should be EACCES. */ if (realpath_error == 0) break; } else { /* * Check permissions in mount table. */ if (newopts(sh->sh_opts)) flavor_count = getclientsflavors_new(sh, cln, flavor_list); else flavor_count = getclientsflavors_old(sh, cln, flavor_list); if (flavor_count != 0) { /* * Found entry in table and * client has correct permissions. */ reply_error = ENOENT; break; } } /* * Check all parent directories. */ dp = strrchr(checkpath, '/'); if (dp == NULL) break; *dp = '\0'; if (strlen(checkpath) == 0) break; /* * Get the real path (no symbolic links in it) */ if (realpath(checkpath, rpath) == NULL) { if (errno != ENOENT) break; } else { realpath_error = 0; } } if (sh) sharefree(sh); free(checkpath); return (reply_error); } /* * We need to inform the caller whether or not we were * able to add a node to the queue. If we are not, then * it is up to the caller to go ahead and log the data. */ static int enqueue_logging_data(char *host, SVCXPRT *transp, char *path, char *rpath, int status, int error) { logging_data *lq; struct netbuf *nb; lq = (logging_data *)calloc(1, sizeof (logging_data)); if (lq == NULL) goto cleanup; /* * We might not yet have the host... */ if (host) { DTRACE_PROBE1(mountd, log_host, host); lq->ld_host = strdup(host); if (lq->ld_host == NULL) goto cleanup; } else { DTRACE_PROBE(mountd, log_no_host); lq->ld_netid = strdup(transp->xp_netid); if (lq->ld_netid == NULL) goto cleanup; lq->ld_nb = calloc(1, sizeof (struct netbuf)); if (lq->ld_nb == NULL) goto cleanup; nb = svc_getrpccaller(transp); if (nb == NULL) { DTRACE_PROBE(mountd, e__nb__enqueue); goto cleanup; } DTRACE_PROBE(mountd, nb_set_enqueue); lq->ld_nb->maxlen = nb->maxlen; lq->ld_nb->len = nb->len; lq->ld_nb->buf = malloc(lq->ld_nb->len); if (lq->ld_nb->buf == NULL) goto cleanup; bcopy(nb->buf, lq->ld_nb->buf, lq->ld_nb->len); } lq->ld_path = strdup(path); if (lq->ld_path == NULL) goto cleanup; if (!error) { lq->ld_rpath = strdup(rpath); if (lq->ld_rpath == NULL) goto cleanup; } lq->ld_status = status; /* * Add to the tail of the logging queue. */ (void) mutex_lock(&logging_queue_lock); if (logging_tail == NULL) { logging_tail = logging_head = lq; } else { logging_tail->ld_next = lq; logging_tail = lq; } (void) cond_signal(&logging_queue_cv); (void) mutex_unlock(&logging_queue_lock); return (TRUE); cleanup: free_logging_data(lq); return (FALSE); } #define CLN_CLNAMES (1 << 0) #define CLN_HOST (1 << 1) static void cln_init_common(struct cln *cln, SVCXPRT *transp, char *netid, struct netbuf *nbuf) { if ((cln->transp = transp) != NULL) { assert(netid == NULL && nbuf == NULL); cln->netid = transp->xp_netid; cln->nbuf = svc_getrpccaller(transp); } else { cln->netid = netid; cln->nbuf = nbuf; } cln->nconf = NULL; cln->clnames = NULL; cln->host = NULL; cln->flags = 0; } void cln_init(struct cln *cln, SVCXPRT *transp) { cln_init_common(cln, transp, NULL, NULL); } void cln_init_lazy(struct cln *cln, char *netid, struct netbuf *nbuf) { cln_init_common(cln, NULL, netid, nbuf); } void cln_fini(struct cln *cln) { if (cln->nconf != NULL) freenetconfigent(cln->nconf); if (cln->clnames != NULL) netdir_free(cln->clnames, ND_HOSTSERVLIST); free(cln->host); } struct netbuf * cln_getnbuf(struct cln *cln) { return (cln->nbuf); } struct nd_hostservlist * cln_getclientsnames(struct cln *cln) { if ((cln->flags & CLN_CLNAMES) == 0) { /* * nconf is not needed if we do not have nbuf (see * cln_gethost() too), so we check for nbuf and in a case it is * NULL we do not try to get nconf. */ if (cln->netid != NULL && cln->nbuf != NULL) { cln->nconf = getnetconfigent(cln->netid); if (cln->nconf == NULL) syslog(LOG_ERR, "%s: getnetconfigent failed", cln->netid); } if (cln->nconf != NULL && cln->nbuf != NULL) (void) __netdir_getbyaddr_nosrv(cln->nconf, &cln->clnames, cln->nbuf); cln->flags |= CLN_CLNAMES; } return (cln->clnames); } /* * Return B_TRUE if the host is already available at no cost */ boolean_t cln_havehost(struct cln *cln) { return ((cln->flags & (CLN_CLNAMES | CLN_HOST)) != 0); } char * cln_gethost(struct cln *cln) { if (cln_getclientsnames(cln) != NULL) return (cln->clnames->h_hostservs[0].h_host); if ((cln->flags & CLN_HOST) == 0) { if (cln->nconf == NULL || cln->nbuf == NULL) { cln->host = strdup("(anon)"); } else { char host[MAXIPADDRLEN]; if (strcmp(cln->nconf->nc_protofmly, NC_INET) == 0) { struct sockaddr_in *sa; /* LINTED pointer alignment */ sa = (struct sockaddr_in *)(cln->nbuf->buf); (void) inet_ntoa_r(sa->sin_addr, host); cln->host = strdup(host); } else if (strcmp(cln->nconf->nc_protofmly, NC_INET6) == 0) { struct sockaddr_in6 *sa; /* LINTED pointer alignment */ sa = (struct sockaddr_in6 *)(cln->nbuf->buf); (void) inet_ntop(AF_INET6, sa->sin6_addr.s6_addr, host, INET6_ADDRSTRLEN); cln->host = strdup(host); } else { syslog(LOG_ERR, gettext("Client's address is " "neither IPv4 nor IPv6")); cln->host = strdup("(anon)"); } } cln->flags |= CLN_HOST; } return (cln->host); } /* * Check mount requests, add to mounted list if ok */ static int mount(struct svc_req *rqstp) { SVCXPRT *transp; int version, vers; struct fhstatus fhs; struct mountres3 mountres3; char fh[FHSIZE3]; int len = FHSIZE3; char *path, rpath[MAXPATHLEN]; share_t *sh = NULL; struct cln cln; char *host = NULL; int error = 0, lofs_tried = 0, enqueued; int flavor_list[MAX_FLAVORS]; int flavor_count; ucred_t *uc = NULL; int audit_status; transp = rqstp->rq_xprt; version = rqstp->rq_vers; path = NULL; if (!svc_getargs(transp, xdr_dirpath, (caddr_t)&path)) { svcerr_decode(transp); return (EACCES); } cln_init(&cln, transp); /* * Put off getting the name for the client until we * need it. This is a performance gain. If we are logging, * then we don't care about performance and might as well * get the host name now in case we need to spit out an * error message. */ if (verbose) { DTRACE_PROBE(mountd, name_by_verbose); if ((host = cln_gethost(&cln)) == NULL) { /* * We failed to get a name for the client, even * 'anon', probably because we ran out of memory. * In this situation it doesn't make sense to * allow the mount to succeed. */ error = EACCES; goto reply; } } /* * If the version being used is less than the minimum version, * the filehandle translation should not be provided to the * client. */ if (rejecting || version < mount_vers_min) { if (verbose) syslog(LOG_NOTICE, "Rejected mount: %s for %s", host, path); error = EACCES; goto reply; } /* * Trusted Extension doesn't support nfsv2. nfsv2 client * uses MOUNT protocol v1 and v2. To prevent circumventing * TX label policy via using nfsv2 client, reject a mount * request with version less than 3 and log an error. */ if (is_system_labeled()) { if (version < 3) { if (verbose) syslog(LOG_ERR, "Rejected mount: TX doesn't support NFSv2"); error = EACCES; goto reply; } } /* * Get the real path (no symbolic links in it) */ if (realpath(path, rpath) == NULL) { error = errno; if (verbose) syslog(LOG_ERR, "mount request: realpath: %s: %m", path); if (error == ENOENT) error = mount_enoent_error(&cln, path, rpath, flavor_list); goto reply; } if ((sh = findentry(rpath)) == NULL && (sh = find_lofsentry(rpath, &lofs_tried)) == NULL) { error = EACCES; goto reply; } /* * Check if this is a "nosub" only export, in which case, mounting * subdirectories isn't allowed. Bug 1184573. */ if (checkrootmount(sh, rpath) == 0) { error = EACCES; goto reply; } if (newopts(sh->sh_opts)) flavor_count = getclientsflavors_new(sh, &cln, flavor_list); else flavor_count = getclientsflavors_old(sh, &cln, flavor_list); if (flavor_count == 0) { error = EACCES; goto reply; } /* * Check MAC policy here. The server side policy should be * consistent with client side mount policy, i.e. * - we disallow an admin_low unlabeled client to mount * - we disallow mount from a lower labeled client. */ if (is_system_labeled()) { m_label_t *clabel = NULL; m_label_t *slabel = NULL; m_label_t admin_low; if (svc_getcallerucred(rqstp->rq_xprt, &uc) != 0) { syslog(LOG_ERR, "mount request: Failed to get caller's ucred : %m"); error = EACCES; goto reply; } if ((clabel = ucred_getlabel(uc)) == NULL) { syslog(LOG_ERR, "mount request: can't get client label from ucred"); error = EACCES; goto reply; } bsllow(&admin_low); if (blequal(&admin_low, clabel)) { struct sockaddr *ca; tsol_tpent_t *tp; ca = (struct sockaddr *)(void *)svc_getrpccaller( rqstp->rq_xprt)->buf; if (ca == NULL) { error = EACCES; goto reply; } /* * get trusted network template associated * with the client. */ tp = get_client_template(ca); if (tp == NULL || tp->host_type != SUN_CIPSO) { if (tp != NULL) tsol_freetpent(tp); error = EACCES; goto reply; } tsol_freetpent(tp); } else { if ((slabel = m_label_alloc(MAC_LABEL)) == NULL) { error = EACCES; goto reply; } if (getlabel(rpath, slabel) != 0) { m_label_free(slabel); error = EACCES; goto reply; } if (!bldominates(clabel, slabel)) { m_label_free(slabel); error = EACCES; goto reply; } m_label_free(slabel); } } /* * Now get the filehandle. * * NFS V2 clients get a 32 byte filehandle. * NFS V3 clients get a 32 or 64 byte filehandle, depending on * the embedded FIDs. */ vers = (version == MOUNTVERS3) ? NFS_V3 : NFS_VERSION; /* LINTED pointer alignment */ while (nfs_getfh(rpath, vers, &len, fh) < 0) { if (errno == EINVAL && (sh = find_lofsentry(rpath, &lofs_tried)) != NULL) { errno = 0; continue; } error = errno == EINVAL ? EACCES : errno; syslog(LOG_DEBUG, "mount request: getfh failed on %s: %m", path); break; } if (version == MOUNTVERS3) { mountres3.mountres3_u.mountinfo.fhandle.fhandle3_len = len; mountres3.mountres3_u.mountinfo.fhandle.fhandle3_val = fh; } else { bcopy(fh, &fhs.fhstatus_u.fhs_fhandle, NFS_FHSIZE); } reply: if (uc != NULL) ucred_free(uc); switch (version) { case MOUNTVERS: case MOUNTVERS_POSIX: if (error == EINVAL) fhs.fhs_status = NFSERR_ACCES; else if (error == EREMOTE) fhs.fhs_status = NFSERR_REMOTE; else fhs.fhs_status = error; if (!svc_sendreply(transp, xdr_fhstatus, (char *)&fhs)) log_cant_reply_cln(&cln); audit_status = fhs.fhs_status; break; case MOUNTVERS3: if (!error) { mountres3.mountres3_u.mountinfo.auth_flavors.auth_flavors_val = flavor_list; mountres3.mountres3_u.mountinfo.auth_flavors.auth_flavors_len = flavor_count; } else if (error == ENAMETOOLONG) error = MNT3ERR_NAMETOOLONG; mountres3.fhs_status = error; if (!svc_sendreply(transp, xdr_mountres3, (char *)&mountres3)) log_cant_reply_cln(&cln); audit_status = mountres3.fhs_status; break; } if (cln_havehost(&cln)) host = cln_gethost(&cln); if (verbose) syslog(LOG_NOTICE, "MOUNT: %s %s %s", (host == NULL) ? "unknown host" : host, error ? "denied" : "mounted", path); /* * If we can not create a queue entry, go ahead and do it * in the context of this thread. */ enqueued = enqueue_logging_data(host, transp, path, rpath, audit_status, error); if (enqueued == FALSE) { if (host == NULL) { DTRACE_PROBE(mountd, name_by_in_thread); host = cln_gethost(&cln); } DTRACE_PROBE(mountd, logged_in_thread); audit_mountd_mount(host, path, audit_status); /* BSM */ if (!error) mntlist_new(host, rpath); /* add entry to mount list */ } if (path != NULL) (void) svc_freeargs(transp, xdr_dirpath, (caddr_t)&path); if (sh) sharefree(sh); cln_fini(&cln); return (error); } /* * Determine whether two paths are within the same file system. * Returns nonzero (true) if paths are the same, zero (false) if * they are different. If an error occurs, return false. * * Use the actual FSID if it's available (via getattrat()); otherwise, * fall back on st_dev. * * With ZFS snapshots, st_dev differs from the regular file system * versus the snapshot. But the fsid is the same throughout. Thus * the fsid is a better test. */ static int same_file_system(const char *path1, const char *path2) { uint64_t fsid1, fsid2; struct stat64 st1, st2; nvlist_t *nvl1 = NULL; nvlist_t *nvl2 = NULL; if ((getattrat(AT_FDCWD, XATTR_VIEW_READONLY, path1, &nvl1) == 0) && (getattrat(AT_FDCWD, XATTR_VIEW_READONLY, path2, &nvl2) == 0) && (nvlist_lookup_uint64(nvl1, A_FSID, &fsid1) == 0) && (nvlist_lookup_uint64(nvl2, A_FSID, &fsid2) == 0)) { nvlist_free(nvl1); nvlist_free(nvl2); /* * We have found fsid's for both paths. */ if (fsid1 == fsid2) return (B_TRUE); return (B_FALSE); } nvlist_free(nvl1); nvlist_free(nvl2); /* * We were unable to find fsid's for at least one of the paths. * fall back on st_dev. */ if (stat64(path1, &st1) < 0) { syslog(LOG_NOTICE, "%s: %m", path1); return (B_FALSE); } if (stat64(path2, &st2) < 0) { syslog(LOG_NOTICE, "%s: %m", path2); return (B_FALSE); } if (st1.st_dev == st2.st_dev) return (B_TRUE); return (B_FALSE); } share_t * findentry(char *path) { share_t *sh = NULL; struct sh_list *shp; char *p1, *p2; check_sharetab(); (void) rw_rdlock(&sharetab_lock); for (shp = share_list; shp; shp = shp->shl_next) { sh = shp->shl_sh; for (p1 = sh->sh_path, p2 = path; *p1 == *p2; p1++, p2++) if (*p1 == '\0') goto done; /* exact match */ /* * Now compare the pathnames for three cases: * * Parent: /export/foo (no trailing slash on parent) * Child: /export/foo/bar * * Parent: /export/foo/ (trailing slash on parent) * Child: /export/foo/bar * * Parent: /export/foo/ (no trailing slash on child) * Child: /export/foo */ if ((*p1 == '\0' && *p2 == '/') || (*p1 == '\0' && *(p1-1) == '/') || (*p2 == '\0' && *p1 == '/' && *(p1+1) == '\0')) { /* * We have a subdirectory. Test whether the * subdirectory is in the same file system. */ if (same_file_system(path, sh->sh_path)) goto done; } } done: sh = shp ? sharedup(sh) : NULL; (void) rw_unlock(&sharetab_lock); return (sh); } static int is_substring(char **mntp, char **path) { char *p1 = *mntp, *p2 = *path; if (*p1 == '\0' && *p2 == '\0') /* exact match */ return (1); else if (*p1 == '\0' && *p2 == '/') return (1); else if (*p1 == '\0' && *(p1-1) == '/') { *path = --p2; /* we need the slash in p2 */ return (1); } else if (*p2 == '\0') { while (*p1 == '/') p1++; if (*p1 == '\0') /* exact match */ return (1); } return (0); } /* * find_lofsentry() searches for the real path which this requested LOFS path * (rpath) shadows. If found, it will return the sharetab entry of * the real path that corresponds to the LOFS path. * We first search mnttab to see if the requested path is an automounted * path. If it is an automounted path, it will trigger the mount by stat()ing * the requested path. Note that it is important to check that this path is * actually an automounted path, otherwise we would stat() a path which may * turn out to be NFS and block indefinitely on a dead server. The automounter * times-out if the server is dead, so there's no risk of hanging this * thread waiting for stat(). * After the mount has been triggered (if necessary), we look for a * mountpoint of type LOFS (by searching /etc/mnttab again) which * is a substring of the rpath. If found, we construct a new path by * concatenating the mnt_special and the remaining of rpath, call findentry() * to make sure the 'real path' is shared. */ static share_t * find_lofsentry(char *rpath, int *done_flag) { struct stat r_stbuf; mntlist_t *ml, *mntl, *mntpnt = NULL; share_t *retcode = NULL; char tmp_path[MAXPATHLEN]; int mntpnt_len = 0, tmp; char *p1, *p2; if ((*done_flag)++) return (retcode); /* * While fsgetmntlist() uses lockf() to * lock the mnttab before reading it in, * the lock ignores threads in the same process. * Read in the mnttab with the protection of a mutex. */ (void) mutex_lock(&mnttab_lock); mntl = fsgetmntlist(); (void) mutex_unlock(&mnttab_lock); /* * Obtain the mountpoint for the requested path. */ for (ml = mntl; ml; ml = ml->mntl_next) { for (p1 = ml->mntl_mnt->mnt_mountp, p2 = rpath; *p1 == *p2 && *p1; p1++, p2++) ; if (is_substring(&p1, &p2) && (tmp = strlen(ml->mntl_mnt->mnt_mountp)) >= mntpnt_len) { mntpnt = ml; mntpnt_len = tmp; } } /* * If the path needs to be autoFS mounted, trigger the mount by * stat()ing it. This is determined by checking whether the * mountpoint we just found is of type autofs. */ if (mntpnt != NULL && strcmp(mntpnt->mntl_mnt->mnt_fstype, "autofs") == 0) { /* * The requested path is a substring of an autoFS filesystem. * Trigger the mount. */ if (stat(rpath, &r_stbuf) < 0) { if (verbose) syslog(LOG_NOTICE, "%s: %m", rpath); goto done; } if ((r_stbuf.st_mode & S_IFMT) == S_IFDIR) { /* * The requested path is a directory, stat(2) it * again with a trailing '.' to force the autoFS * module to trigger the mount of indirect * automount entries, such as /net/jurassic/. */ if (strlen(rpath) + 2 > MAXPATHLEN) { if (verbose) { syslog(LOG_NOTICE, "%s/.: exceeds MAXPATHLEN %d", rpath, MAXPATHLEN); } goto done; } (void) strcpy(tmp_path, rpath); (void) strcat(tmp_path, "/."); if (stat(tmp_path, &r_stbuf) < 0) { if (verbose) syslog(LOG_NOTICE, "%s: %m", tmp_path); goto done; } } /* * The mount has been triggered, re-read mnttab to pick up * the changes made by autoFS. */ fsfreemntlist(mntl); (void) mutex_lock(&mnttab_lock); mntl = fsgetmntlist(); (void) mutex_unlock(&mnttab_lock); } /* * The autoFS mountpoint has been triggered if necessary, * now search mnttab again to determine if the requested path * is an LOFS mount of a shared path. */ mntpnt_len = 0; for (ml = mntl; ml; ml = ml->mntl_next) { if (strcmp(ml->mntl_mnt->mnt_fstype, "lofs")) continue; for (p1 = ml->mntl_mnt->mnt_mountp, p2 = rpath; *p1 == *p2 && *p1; p1++, p2++) ; if (is_substring(&p1, &p2) && ((tmp = strlen(ml->mntl_mnt->mnt_mountp)) >= mntpnt_len)) { mntpnt_len = tmp; if ((strlen(ml->mntl_mnt->mnt_special) + strlen(p2)) > MAXPATHLEN) { if (verbose) { syslog(LOG_NOTICE, "%s%s: exceeds %d", ml->mntl_mnt->mnt_special, p2, MAXPATHLEN); } if (retcode) sharefree(retcode); retcode = NULL; goto done; } (void) strcpy(tmp_path, ml->mntl_mnt->mnt_special); (void) strcat(tmp_path, p2); if (retcode) sharefree(retcode); retcode = findentry(tmp_path); } } if (retcode) { assert(strlen(tmp_path) > 0); (void) strcpy(rpath, tmp_path); } done: fsfreemntlist(mntl); return (retcode); } /* * Determine whether an access list grants rights to a particular host. * We match on aliases of the hostname as well as on the canonical name. * Names in the access list may be either hosts or netgroups; they're * not distinguished syntactically. We check for hosts first because * it's cheaper, then try netgroups. * * Return values: * 1 - access is granted * 0 - access is denied * -1 - an error occured */ int in_access_list(struct cln *cln, char *access_list) /* N.B. we clobber this "input" parameter */ { char addr[INET_ADDRSTRLEN]; char buff[256]; int nentries = 0; char *cstr = access_list; char *gr = access_list; int i; int response; int ret; struct netbuf *pnb; struct nd_hostservlist *clnames = NULL; /* If no access list - then it's unrestricted */ if (access_list == NULL || *access_list == '\0') return (1); if ((pnb = cln_getnbuf(cln)) == NULL) return (-1); for (;;) { if ((cstr = strpbrk(cstr, "[:")) != NULL) { if (*cstr == ':') { *cstr = '\0'; } else { assert(*cstr == '['); cstr = strchr(cstr + 1, ']'); if (cstr == NULL) return (-1); cstr++; continue; } } /* * If the list name has a '-' prepended then a match of * the following name implies failure instead of success. */ if (*gr == '-') { response = 0; gr++; } else { response = 1; } /* * First check if we have '@' entry, as it doesn't * require client hostname. */ if (*gr == '@') { gr++; /* Netname support */ if (!isdigit(*gr) && *gr != '[') { struct netent n, *np; if ((np = getnetbyname_r(gr, &n, buff, sizeof (buff))) != NULL && np->n_net != 0) { while ((np->n_net & 0xFF000000u) == 0) np->n_net <<= 8; np->n_net = htonl(np->n_net); if (inet_ntop(AF_INET, &np->n_net, addr, INET_ADDRSTRLEN) == NULL) break; ret = inet_matchaddr(pnb->buf, addr); if (ret == -1) { if (errno == EINVAL) { syslog(LOG_WARNING, "invalid access " "list entry: %s", addr); } return (-1); } else if (ret == 1) { return (response); } } } else { ret = inet_matchaddr(pnb->buf, gr); if (ret == -1) { if (errno == EINVAL) { syslog(LOG_WARNING, "invalid access list " "entry: %s", gr); } return (-1); } else if (ret == 1) { return (response); } } goto next; } /* * No other checks can be performed if client address * can't be resolved. */ if ((clnames = cln_getclientsnames(cln)) == NULL) goto next; /* Otherwise loop through all client hostname aliases */ for (i = 0; i < clnames->h_cnt; i++) { char *host = clnames->h_hostservs[i].h_host; /* * If the list name begins with a dot then * do a domain name suffix comparison. * A single dot matches any name with no * suffix. */ if (*gr == '.') { if (*(gr + 1) == '\0') { /* single dot */ if (strchr(host, '.') == NULL) return (response); } else { int off = strlen(host) - strlen(gr); if (off > 0 && strcasecmp(host + off, gr) == 0) { return (response); } } } else { /* Just do a hostname match */ if (strcasecmp(gr, host) == 0) return (response); } } nentries++; next: if (cstr == NULL) break; gr = ++cstr; } if (clnames == NULL) return (0); return (netgroup_check(clnames, access_list, nentries)); } static char *optlist[] = { #define OPT_RO 0 SHOPT_RO, #define OPT_RW 1 SHOPT_RW, #define OPT_ROOT 2 SHOPT_ROOT, #define OPT_SECURE 3 SHOPT_SECURE, #define OPT_ANON 4 SHOPT_ANON, #define OPT_WINDOW 5 SHOPT_WINDOW, #define OPT_NOSUID 6 SHOPT_NOSUID, #define OPT_ACLOK 7 SHOPT_ACLOK, #define OPT_SEC 8 SHOPT_SEC, #define OPT_NONE 9 SHOPT_NONE, #define OPT_UIDMAP 10 SHOPT_UIDMAP, #define OPT_GIDMAP 11 SHOPT_GIDMAP, NULL }; static int map_flavor(char *str) { seconfig_t sec; if (nfs_getseconfig_byname(str, &sec)) return (-1); return (sec.sc_nfsnum); } /* * If the option string contains a "sec=" * option, then use new option syntax. */ static int newopts(char *opts) { char *head, *p, *val; if (!opts || *opts == '\0') return (0); head = strdup(opts); if (head == NULL) { syslog(LOG_ERR, "opts: no memory"); return (0); } p = head; while (*p) { if (getsubopt(&p, optlist, &val) == OPT_SEC) { free(head); return (1); } } free(head); return (0); } /* * Given an export and the clients hostname(s) * determine the security flavors that this * client is permitted to use. * * This routine is called only for "old" syntax, i.e. * only one security flavor is allowed. So we need * to determine two things: the particular flavor, * and whether the client is allowed to use this * flavor, i.e. is in the access list. * * Note that if there is no access list, then the * default is that access is granted. */ static int getclientsflavors_old(share_t *sh, struct cln *cln, int *flavors) { char *opts, *p, *val; boolean_t ok = B_FALSE; int defaultaccess = 1; boolean_t reject = B_FALSE; opts = strdup(sh->sh_opts); if (opts == NULL) { syslog(LOG_ERR, "getclientsflavors: no memory"); return (0); } flavors[0] = AUTH_SYS; p = opts; while (*p) { switch (getsubopt(&p, optlist, &val)) { case OPT_SECURE: flavors[0] = AUTH_DES; break; case OPT_RO: case OPT_RW: defaultaccess = 0; if (in_access_list(cln, val) > 0) ok = B_TRUE; break; case OPT_NONE: defaultaccess = 0; if (in_access_list(cln, val) > 0) reject = B_TRUE; } } free(opts); /* none takes precedence over everything else */ if (reject) ok = B_FALSE; return (defaultaccess || ok); } /* * Given an export and the clients hostname(s) * determine the security flavors that this * client is permitted to use. * * This is somewhat more complicated than the "old" * routine because the options may contain multiple * security flavors (sec=) each with its own access * lists. So a client could be granted access based * on a number of security flavors. Note that the * type of access might not always be the same, the * client may get readonly access with one flavor * and readwrite with another, however the client * is not told this detail, it gets only the list * of flavors, and only if the client is using * version 3 of the mount protocol. */ static int getclientsflavors_new(share_t *sh, struct cln *cln, int *flavors) { char *opts, *p, *val; char *lasts; char *f; boolean_t defaultaccess = B_TRUE; /* default access is rw */ boolean_t access_ok = B_FALSE; int count, c; boolean_t reject = B_FALSE; opts = strdup(sh->sh_opts); if (opts == NULL) { syslog(LOG_ERR, "getclientsflavors: no memory"); return (0); } p = opts; count = c = 0; while (*p) { switch (getsubopt(&p, optlist, &val)) { case OPT_SEC: if (reject) access_ok = B_FALSE; /* * Before a new sec=xxx option, check if we need * to move the c index back to the previous count. */ if (!defaultaccess && !access_ok) { c = count; } /* get all the sec=f1[:f2] flavors */ while ((f = strtok_r(val, ":", &lasts)) != NULL) { flavors[c++] = map_flavor(f); val = NULL; } /* for a new sec=xxx option, default is rw access */ defaultaccess = B_TRUE; access_ok = B_FALSE; reject = B_FALSE; break; case OPT_RO: case OPT_RW: defaultaccess = B_FALSE; if (in_access_list(cln, val) > 0) access_ok = B_TRUE; break; case OPT_NONE: defaultaccess = B_FALSE; if (in_access_list(cln, val) > 0) reject = B_TRUE; /* none overides rw/ro */ break; } } if (reject) access_ok = B_FALSE; if (!defaultaccess && !access_ok) c = count; free(opts); return (c); } /* * This is a tricky piece of code that parses the * share options looking for a match on the auth * flavor that the client is using. If it finds * a match, then the client is given ro, rw, or * no access depending whether it is in the access * list. There is a special case for "secure" * flavor. Other flavors are values of the new "sec=" option. */ int check_client(share_t *sh, struct cln *cln, int flavor, uid_t clnt_uid, gid_t clnt_gid, uint_t clnt_ngids, gid_t *clnt_gids, uid_t *srv_uid, gid_t *srv_gid, uint_t *srv_ngids, gid_t **srv_gids) { if (newopts(sh->sh_opts)) return (check_client_new(sh, cln, flavor, clnt_uid, clnt_gid, clnt_ngids, clnt_gids, srv_uid, srv_gid, srv_ngids, srv_gids)); else return (check_client_old(sh, cln, flavor, clnt_uid, clnt_gid, clnt_ngids, clnt_gids, srv_uid, srv_gid, srv_ngids, srv_gids)); } extern int _getgroupsbymember(const char *, gid_t[], int, int); /* * Get supplemental groups for uid */ static int getusergroups(uid_t uid, uint_t *ngrps, gid_t **grps) { struct passwd pwd; char *pwbuf = alloca(pw_size); gid_t *tmpgrps = alloca(ngroups_max * sizeof (gid_t)); int tmpngrps; if (getpwuid_r(uid, &pwd, pwbuf, pw_size) == NULL) return (-1); tmpgrps[0] = pwd.pw_gid; tmpngrps = _getgroupsbymember(pwd.pw_name, tmpgrps, ngroups_max, 1); if (tmpngrps <= 0) { syslog(LOG_WARNING, "getusergroups(): Unable to get groups for user %s", pwd.pw_name); return (-1); } *grps = malloc(tmpngrps * sizeof (gid_t)); if (*grps == NULL) { syslog(LOG_ERR, "getusergroups(): Memory allocation failed: %m"); return (-1); } *ngrps = tmpngrps; (void) memcpy(*grps, tmpgrps, tmpngrps * sizeof (gid_t)); return (0); } /* * is_a_number(number) * * is the string a number in one of the forms we want to use? */ static int is_a_number(char *number) { int ret = 1; int hex = 0; if (strncmp(number, "0x", 2) == 0) { number += 2; hex = 1; } else if (*number == '-') { number++; /* skip the minus */ } while (ret == 1 && *number != '\0') { if (hex) { ret = isxdigit(*number++); } else { ret = isdigit(*number++); } } return (ret); } static boolean_t get_uid(char *value, uid_t *uid) { if (!is_a_number(value)) { struct passwd *pw; /* * in this case it would have to be a * user name */ pw = getpwnam(value); if (pw == NULL) return (B_FALSE); *uid = pw->pw_uid; endpwent(); } else { uint64_t intval; intval = strtoull(value, NULL, 0); if (intval > UID_MAX && intval != -1) return (B_FALSE); *uid = (uid_t)intval; } return (B_TRUE); } static boolean_t get_gid(char *value, gid_t *gid) { if (!is_a_number(value)) { struct group *gr; /* * in this case it would have to be a * group name */ gr = getgrnam(value); if (gr == NULL) return (B_FALSE); *gid = gr->gr_gid; endgrent(); } else { uint64_t intval; intval = strtoull(value, NULL, 0); if (intval > UID_MAX && intval != -1) return (B_FALSE); *gid = (gid_t)intval; } return (B_TRUE); } static int check_client_old(share_t *sh, struct cln *cln, int flavor, uid_t clnt_uid, gid_t clnt_gid, uint_t clnt_ngids, gid_t *clnt_gids, uid_t *srv_uid, gid_t *srv_gid, uint_t *srv_ngids, gid_t **srv_gids) { char *opts, *p, *val; int match; /* Set when a flavor is matched */ int perm = 0; /* Set when "ro", "rw" or "root" is matched */ int list = 0; /* Set when "ro", "rw" is found */ int ro_val = 0; /* Set if ro option is 'ro=' */ int rw_val = 0; /* Set if rw option is 'rw=' */ boolean_t map_deny = B_FALSE; opts = strdup(sh->sh_opts); if (opts == NULL) { syslog(LOG_ERR, "check_client: no memory"); return (0); } /* * If client provided 16 supplemental groups with AUTH_SYS, lookup * locally for all of them */ if (flavor == AUTH_SYS && clnt_ngids == NGRPS && ngroups_max > NGRPS) if (getusergroups(clnt_uid, srv_ngids, srv_gids) == 0) perm |= NFSAUTH_GROUPS; p = opts; match = AUTH_UNIX; while (*p) { switch (getsubopt(&p, optlist, &val)) { case OPT_SECURE: match = AUTH_DES; if (perm & NFSAUTH_GROUPS) { free(*srv_gids); *srv_ngids = 0; *srv_gids = NULL; perm &= ~NFSAUTH_GROUPS; } break; case OPT_RO: list++; if (val != NULL) ro_val++; if (in_access_list(cln, val) > 0) perm |= NFSAUTH_RO; break; case OPT_RW: list++; if (val != NULL) rw_val++; if (in_access_list(cln, val) > 0) perm |= NFSAUTH_RW; break; case OPT_ROOT: /* * Check if the client is in * the root list. Only valid * for AUTH_SYS. */ if (flavor != AUTH_SYS) break; if (val == NULL || *val == '\0') break; if (clnt_uid != 0) break; if (in_access_list(cln, val) > 0) { perm |= NFSAUTH_ROOT; perm |= NFSAUTH_UIDMAP | NFSAUTH_GIDMAP; map_deny = B_FALSE; if (perm & NFSAUTH_GROUPS) { free(*srv_gids); *srv_ngids = 0; *srv_gids = NULL; perm &= ~NFSAUTH_GROUPS; } } break; case OPT_NONE: /* * Check if the client should have no access * to this share at all. This option behaves * more like "root" than either "rw" or "ro". */ if (in_access_list(cln, val) > 0) perm |= NFSAUTH_DENIED; break; case OPT_UIDMAP: { char *c; char *n; /* * The uidmap is supported for AUTH_SYS only. */ if (flavor != AUTH_SYS) break; if (perm & NFSAUTH_UIDMAP || map_deny) break; for (c = val; c != NULL; c = n) { char *s; char *al; uid_t srv; n = strchr(c, '~'); if (n != NULL) *n++ = '\0'; s = strchr(c, ':'); if (s != NULL) { *s++ = '\0'; al = strchr(s, ':'); if (al != NULL) *al++ = '\0'; } if (s == NULL || al == NULL) continue; if (*c == '\0') { if (clnt_uid != (uid_t)-1) continue; } else if (strcmp(c, "*") != 0) { uid_t clnt; if (!get_uid(c, &clnt)) continue; if (clnt_uid != clnt) continue; } if (*s == '\0') srv = UID_NOBODY; else if (!get_uid(s, &srv)) continue; else if (srv == (uid_t)-1) { map_deny = B_TRUE; break; } if (in_access_list(cln, al) > 0) { *srv_uid = srv; perm |= NFSAUTH_UIDMAP; if (perm & NFSAUTH_GROUPS) { free(*srv_gids); *srv_ngids = 0; *srv_gids = NULL; perm &= ~NFSAUTH_GROUPS; } break; } } break; } case OPT_GIDMAP: { char *c; char *n; /* * The gidmap is supported for AUTH_SYS only. */ if (flavor != AUTH_SYS) break; if (perm & NFSAUTH_GIDMAP || map_deny) break; for (c = val; c != NULL; c = n) { char *s; char *al; gid_t srv; n = strchr(c, '~'); if (n != NULL) *n++ = '\0'; s = strchr(c, ':'); if (s != NULL) { *s++ = '\0'; al = strchr(s, ':'); if (al != NULL) *al++ = '\0'; } if (s == NULL || al == NULL) break; if (*c == '\0') { if (clnt_gid != (gid_t)-1) continue; } else if (strcmp(c, "*") != 0) { gid_t clnt; if (!get_gid(c, &clnt)) continue; if (clnt_gid != clnt) continue; } if (*s == '\0') srv = UID_NOBODY; else if (!get_gid(s, &srv)) continue; else if (srv == (gid_t)-1) { map_deny = B_TRUE; break; } if (in_access_list(cln, al) > 0) { *srv_gid = srv; perm |= NFSAUTH_GIDMAP; if (perm & NFSAUTH_GROUPS) { free(*srv_gids); *srv_ngids = 0; *srv_gids = NULL; perm &= ~NFSAUTH_GROUPS; } break; } } break; } default: break; } } free(opts); if (perm & NFSAUTH_ROOT) { *srv_uid = 0; *srv_gid = 0; } if (map_deny) perm |= NFSAUTH_DENIED; if (!(perm & NFSAUTH_UIDMAP)) *srv_uid = clnt_uid; if (!(perm & NFSAUTH_GIDMAP)) *srv_gid = clnt_gid; if (flavor != match || perm & NFSAUTH_DENIED) return (NFSAUTH_DENIED); if (list) { /* * If the client doesn't match an "ro" or "rw" * list then set no access. */ if ((perm & (NFSAUTH_RO | NFSAUTH_RW)) == 0) perm |= NFSAUTH_DENIED; } else { /* * The client matched a flavor entry that * has no explicit "rw" or "ro" determination. * Default it to "rw". */ perm |= NFSAUTH_RW; } /* * The client may show up in both ro= and rw= * lists. If so, then turn off the RO access * bit leaving RW access. */ if (perm & NFSAUTH_RO && perm & NFSAUTH_RW) { /* * Logically cover all permutations of rw=,ro=. * In the case where, rw,ro= we would like * to remove RW access for the host. In all other cases * RW wins the precedence battle. */ if (!rw_val && ro_val) { perm &= ~(NFSAUTH_RW); } else { perm &= ~(NFSAUTH_RO); } } return (perm); } /* * Check if the client has access by using a flavor different from * the given "flavor". If "flavor" is not in the flavor list, * return TRUE to indicate that this "flavor" is a wrong sec. */ static bool_t is_wrongsec(share_t *sh, struct cln *cln, int flavor) { int flavor_list[MAX_FLAVORS]; int flavor_count, i; /* get the flavor list that the client has access with */ flavor_count = getclientsflavors_new(sh, cln, flavor_list); if (flavor_count == 0) return (FALSE); /* * Check if the given "flavor" is in the flavor_list. */ for (i = 0; i < flavor_count; i++) { if (flavor == flavor_list[i]) return (FALSE); } /* * If "flavor" is not in the flavor_list, return TRUE to indicate * that the client should have access by using a security flavor * different from this "flavor". */ return (TRUE); } /* * Given an export and the client's hostname, we * check the security options to see whether the * client is allowed to use the given security flavor. * * The strategy is to proceed through the options looking * for a flavor match, then pay attention to the ro, rw, * and root options. * * Note that an entry may list several flavors in a * single entry, e.g. * * sec=krb5,rw=clnt1:clnt2,ro,sec=sys,ro * */ static int check_client_new(share_t *sh, struct cln *cln, int flavor, uid_t clnt_uid, gid_t clnt_gid, uint_t clnt_ngids, gid_t *clnt_gids, uid_t *srv_uid, gid_t *srv_gid, uint_t *srv_ngids, gid_t **srv_gids) { char *opts, *p, *val; char *lasts; char *f; int match = 0; /* Set when a flavor is matched */ int perm = 0; /* Set when "ro", "rw" or "root" is matched */ int list = 0; /* Set when "ro", "rw" is found */ int ro_val = 0; /* Set if ro option is 'ro=' */ int rw_val = 0; /* Set if rw option is 'rw=' */ boolean_t map_deny = B_FALSE; opts = strdup(sh->sh_opts); if (opts == NULL) { syslog(LOG_ERR, "check_client: no memory"); return (0); } /* * If client provided 16 supplemental groups with AUTH_SYS, lookup * locally for all of them */ if (flavor == AUTH_SYS && clnt_ngids == NGRPS && ngroups_max > NGRPS) if (getusergroups(clnt_uid, srv_ngids, srv_gids) == 0) perm |= NFSAUTH_GROUPS; p = opts; while (*p) { switch (getsubopt(&p, optlist, &val)) { case OPT_SEC: if (match) goto done; while ((f = strtok_r(val, ":", &lasts)) != NULL) { if (flavor == map_flavor(f)) { match = 1; break; } val = NULL; } break; case OPT_RO: if (!match) break; list++; if (val != NULL) ro_val++; if (in_access_list(cln, val) > 0) perm |= NFSAUTH_RO; break; case OPT_RW: if (!match) break; list++; if (val != NULL) rw_val++; if (in_access_list(cln, val) > 0) perm |= NFSAUTH_RW; break; case OPT_ROOT: /* * Check if the client is in * the root list. Only valid * for AUTH_SYS. */ if (flavor != AUTH_SYS) break; if (!match) break; if (val == NULL || *val == '\0') break; if (clnt_uid != 0) break; if (in_access_list(cln, val) > 0) { perm |= NFSAUTH_ROOT; perm |= NFSAUTH_UIDMAP | NFSAUTH_GIDMAP; map_deny = B_FALSE; if (perm & NFSAUTH_GROUPS) { free(*srv_gids); *srv_gids = NULL; *srv_ngids = 0; perm &= ~NFSAUTH_GROUPS; } } break; case OPT_NONE: /* * Check if the client should have no access * to this share at all. This option behaves * more like "root" than either "rw" or "ro". */ if (in_access_list(cln, val) > 0) perm |= NFSAUTH_DENIED; break; case OPT_UIDMAP: { char *c; char *n; /* * The uidmap is supported for AUTH_SYS only. */ if (flavor != AUTH_SYS) break; if (!match || perm & NFSAUTH_UIDMAP || map_deny) break; for (c = val; c != NULL; c = n) { char *s; char *al; uid_t srv; n = strchr(c, '~'); if (n != NULL) *n++ = '\0'; s = strchr(c, ':'); if (s != NULL) { *s++ = '\0'; al = strchr(s, ':'); if (al != NULL) *al++ = '\0'; } if (s == NULL || al == NULL) continue; if (*c == '\0') { if (clnt_uid != (uid_t)-1) continue; } else if (strcmp(c, "*") != 0) { uid_t clnt; if (!get_uid(c, &clnt)) continue; if (clnt_uid != clnt) continue; } if (*s == '\0') srv = UID_NOBODY; else if (!get_uid(s, &srv)) continue; else if (srv == (uid_t)-1) { map_deny = B_TRUE; break; } if (in_access_list(cln, al) > 0) { *srv_uid = srv; perm |= NFSAUTH_UIDMAP; if (perm & NFSAUTH_GROUPS) { free(*srv_gids); *srv_gids = NULL; *srv_ngids = 0; perm &= ~NFSAUTH_GROUPS; } break; } } break; } case OPT_GIDMAP: { char *c; char *n; /* * The gidmap is supported for AUTH_SYS only. */ if (flavor != AUTH_SYS) break; if (!match || perm & NFSAUTH_GIDMAP || map_deny) break; for (c = val; c != NULL; c = n) { char *s; char *al; gid_t srv; n = strchr(c, '~'); if (n != NULL) *n++ = '\0'; s = strchr(c, ':'); if (s != NULL) { *s++ = '\0'; al = strchr(s, ':'); if (al != NULL) *al++ = '\0'; } if (s == NULL || al == NULL) break; if (*c == '\0') { if (clnt_gid != (gid_t)-1) continue; } else if (strcmp(c, "*") != 0) { gid_t clnt; if (!get_gid(c, &clnt)) continue; if (clnt_gid != clnt) continue; } if (*s == '\0') srv = UID_NOBODY; else if (!get_gid(s, &srv)) continue; else if (srv == (gid_t)-1) { map_deny = B_TRUE; break; } if (in_access_list(cln, al) > 0) { *srv_gid = srv; perm |= NFSAUTH_GIDMAP; if (perm & NFSAUTH_GROUPS) { free(*srv_gids); *srv_gids = NULL; *srv_ngids = 0; perm &= ~NFSAUTH_GROUPS; } break; } } break; } default: break; } } done: if (perm & NFSAUTH_ROOT) { *srv_uid = 0; *srv_gid = 0; } if (map_deny) perm |= NFSAUTH_DENIED; if (!(perm & NFSAUTH_UIDMAP)) *srv_uid = clnt_uid; if (!(perm & NFSAUTH_GIDMAP)) *srv_gid = clnt_gid; /* * If no match then set the perm accordingly */ if (!match || perm & NFSAUTH_DENIED) { free(opts); return (NFSAUTH_DENIED); } if (list) { /* * If the client doesn't match an "ro" or "rw" list then * check if it may have access by using a different flavor. * If so, return NFSAUTH_WRONGSEC. * If not, return NFSAUTH_DENIED. */ if ((perm & (NFSAUTH_RO | NFSAUTH_RW)) == 0) { if (is_wrongsec(sh, cln, flavor)) perm |= NFSAUTH_WRONGSEC; else perm |= NFSAUTH_DENIED; } } else { /* * The client matched a flavor entry that * has no explicit "rw" or "ro" determination. * Make sure it defaults to "rw". */ perm |= NFSAUTH_RW; } /* * The client may show up in both ro= and rw= * lists. If so, then turn off the RO access * bit leaving RW access. */ if (perm & NFSAUTH_RO && perm & NFSAUTH_RW) { /* * Logically cover all permutations of rw=,ro=. * In the case where, rw,ro= we would like * to remove RW access for the host. In all other cases * RW wins the precedence battle. */ if (!rw_val && ro_val) { perm &= ~(NFSAUTH_RW); } else { perm &= ~(NFSAUTH_RO); } } free(opts); return (perm); } void check_sharetab() { FILE *f; struct stat st; static timestruc_t last_sharetab_time; timestruc_t prev_sharetab_time; share_t *sh; struct sh_list *shp, *shp_prev; int res, c = 0; /* * read in /etc/dfs/sharetab if it has changed */ if (stat(SHARETAB, &st) != 0) { syslog(LOG_ERR, "Cannot stat %s: %m", SHARETAB); return; } if (st.st_mtim.tv_sec == last_sharetab_time.tv_sec && st.st_mtim.tv_nsec == last_sharetab_time.tv_nsec) { /* * No change. */ return; } /* * Remember the mod time, then after getting the * write lock check again. If another thread * already did the update, then there's no * work to do. */ prev_sharetab_time = last_sharetab_time; (void) rw_wrlock(&sharetab_lock); if (prev_sharetab_time.tv_sec != last_sharetab_time.tv_sec || prev_sharetab_time.tv_nsec != last_sharetab_time.tv_nsec) { (void) rw_unlock(&sharetab_lock); return; } /* * Note that since the sharetab is now in memory * and a snapshot is taken, we no longer have to * lock the file. */ f = fopen(SHARETAB, "r"); if (f == NULL) { syslog(LOG_ERR, "Cannot open %s: %m", SHARETAB); (void) rw_unlock(&sharetab_lock); return; } /* * Once we are sure /etc/dfs/sharetab has been * modified, flush netgroup cache entries. */ netgrp_cache_flush(); sh_free(share_list); /* free old list */ share_list = NULL; while ((res = getshare(f, &sh)) > 0) { c++; if (strcmp(sh->sh_fstype, "nfs") != 0) continue; shp = malloc(sizeof (*shp)); if (shp == NULL) goto alloc_failed; if (share_list == NULL) share_list = shp; else /* LINTED not used before set */ shp_prev->shl_next = shp; shp_prev = shp; shp->shl_next = NULL; shp->shl_sh = sharedup(sh); if (shp->shl_sh == NULL) goto alloc_failed; } if (res < 0) syslog(LOG_ERR, "%s: invalid at line %d\n", SHARETAB, c + 1); if (stat(SHARETAB, &st) != 0) { syslog(LOG_ERR, "Cannot stat %s: %m", SHARETAB); (void) fclose(f); (void) rw_unlock(&sharetab_lock); return; } last_sharetab_time = st.st_mtim; (void) fclose(f); (void) rw_unlock(&sharetab_lock); return; alloc_failed: syslog(LOG_ERR, "check_sharetab: no memory"); sh_free(share_list); share_list = NULL; (void) fclose(f); (void) rw_unlock(&sharetab_lock); } static void sh_free(struct sh_list *shp) { struct sh_list *next; while (shp) { sharefree(shp->shl_sh); next = shp->shl_next; free(shp); shp = next; } } /* * Remove an entry from mounted list */ static void umount(struct svc_req *rqstp) { char *host, *path, *remove_path; char rpath[MAXPATHLEN]; SVCXPRT *transp; struct cln cln; transp = rqstp->rq_xprt; path = NULL; if (!svc_getargs(transp, xdr_dirpath, (caddr_t)&path)) { svcerr_decode(transp); return; } cln_init(&cln, transp); errno = 0; if (!svc_sendreply(transp, xdr_void, (char *)NULL)) log_cant_reply_cln(&cln); host = cln_gethost(&cln); if (host == NULL) { /* * Without the hostname we can't do audit or delete * this host from the mount entries. */ (void) svc_freeargs(transp, xdr_dirpath, (caddr_t)&path); return; } if (verbose) syslog(LOG_NOTICE, "UNMOUNT: %s unmounted %s", host, path); audit_mountd_umount(host, path); remove_path = rpath; /* assume we will use the cannonical path */ if (realpath(path, rpath) == NULL) { if (verbose) syslog(LOG_WARNING, "UNMOUNT: realpath: %s: %m ", path); remove_path = path; /* use path provided instead */ } mntlist_delete(host, remove_path); /* remove from mount list */ cln_fini(&cln); (void) svc_freeargs(transp, xdr_dirpath, (caddr_t)&path); } /* * Remove all entries for one machine from mounted list */ static void umountall(struct svc_req *rqstp) { SVCXPRT *transp; char *host; struct cln cln; transp = rqstp->rq_xprt; if (!svc_getargs(transp, xdr_void, NULL)) { svcerr_decode(transp); return; } /* * We assume that this call is asynchronous and made via rpcbind * callit routine. Therefore return control immediately. The error * causes rpcbind to remain silent, as opposed to every machine * on the net blasting the requester with a response. */ svcerr_systemerr(transp); cln_init(&cln, transp); host = cln_gethost(&cln); if (host == NULL) { /* Can't do anything without the name of the client */ return; } /* * Remove all hosts entries from mount list */ mntlist_delete_all(host); if (verbose) syslog(LOG_NOTICE, "UNMOUNTALL: from %s", host); cln_fini(&cln); } void * exmalloc(size_t size) { void *ret; if ((ret = malloc(size)) == NULL) { syslog(LOG_ERR, "Out of memory"); exit(1); } return (ret); } static tsol_tpent_t * get_client_template(struct sockaddr *sock) { in_addr_t v4client; in6_addr_t v6client; char v4_addr[INET_ADDRSTRLEN]; char v6_addr[INET6_ADDRSTRLEN]; tsol_rhent_t *rh; tsol_tpent_t *tp; switch (sock->sa_family) { case AF_INET: v4client = ((struct sockaddr_in *)(void *)sock)-> sin_addr.s_addr; if (inet_ntop(AF_INET, &v4client, v4_addr, INET_ADDRSTRLEN) == NULL) return (NULL); rh = tsol_getrhbyaddr(v4_addr, sizeof (v4_addr), AF_INET); if (rh == NULL) return (NULL); tp = tsol_gettpbyname(rh->rh_template); tsol_freerhent(rh); return (tp); break; case AF_INET6: v6client = ((struct sockaddr_in6 *)(void *)sock)->sin6_addr; if (inet_ntop(AF_INET6, &v6client, v6_addr, INET6_ADDRSTRLEN) == NULL) return (NULL); rh = tsol_getrhbyaddr(v6_addr, sizeof (v6_addr), AF_INET6); if (rh == NULL) return (NULL); tp = tsol_gettpbyname(rh->rh_template); tsol_freerhent(rh); return (tp); break; default: return (NULL); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MOUNTD_H #define _MOUNTD_H #include #include #ifdef __cplusplus extern "C" { #endif #define MAXIPADDRLEN 512 struct nd_hostservlist; extern void rmtab_load(void); extern void mntlist_send(SVCXPRT *transp); extern void mntlist_new(char *host, char *path); extern void mntlist_delete(char *host, char *path); extern void mntlist_delete_all(char *host); extern void netgroup_init(void); extern int netgroup_check(struct nd_hostservlist *, char *, int); extern void netgrp_cache_flush(void); extern void export(struct svc_req *); extern void nfsauth_func(void *, char *, size_t, door_desc_t *, uint_t); extern char *inet_ntoa_r(struct in_addr, char *); extern int nfs_getfh(char *, int, int *, char *); extern void nfsauth_prog(struct svc_req *, SVCXPRT *); extern struct sh_list *share_list; extern rwlock_t sharetab_lock; extern void check_sharetab(void); struct cln; extern void log_cant_reply(SVCXPRT *); extern void log_cant_reply_cln(struct cln *); extern void *exmalloc(size_t); extern struct share *findentry(char *); extern int check_client(struct share *, struct cln *, int, uid_t, gid_t, uint_t, gid_t *, uid_t *, gid_t *, uint_t *, gid_t **); extern int in_access_list(struct cln *, char *); struct cln { SVCXPRT *transp; char *netid; struct netconfig *nconf; struct netbuf *nbuf; struct nd_hostservlist *clnames; char *host; int flags; }; extern void cln_init(struct cln *, SVCXPRT *); extern void cln_init_lazy(struct cln *, char *, struct netbuf *); extern void cln_fini(struct cln *); extern struct netbuf *cln_getnbuf(struct cln *); extern struct nd_hostservlist *cln_getclientsnames(struct cln *); extern boolean_t cln_havehost(struct cln *); extern char *cln_gethost(struct cln *); /* * These functions are defined here due to the fact * that we can not find the proper header file to * include. These functions are, at present, not * listed in any other header files. */ /* * These three functions are hidden functions in the * bsm libraries (libbsm). */ extern void audit_mountd_setup(void); extern void audit_mountd_mount(char *, char *, int); extern void audit_mountd_umount(char *, char *); /* * This appears to be a hidden function in libc. * Private interface to nss_search(). * Accepts N strings rather than 1. */ extern int __multi_innetgr(); #ifdef __cplusplus } #endif #endif /* _MOUNTD_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ provider mountd { probe hashset(int, float); probe e__nb__enqueue(void); probe nb_set_enqueue(void); probe log_host(char *); probe log_no_host(void); probe logging_cleared(int); probe logged_in_thread(void); probe name_by_verbose(void); probe name_by_in_thread(void); probe name_by_lazy(void); probe name_by_addrlist(void); probe name_by_netgroup(void); }; #pragma D attributes Private/Private/Common provider mountd provider #pragma D attributes Private/Private/Common provider mountd module #pragma D attributes Private/Private/Common provider mountd function #pragma D attributes Private/Private/Common provider mountd name #pragma D attributes Private/Private/Common provider mountd args /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../lib/sharetab.h" #include "mountd.h" struct cache_entry { char *cache_host; time_t cache_time; int cache_belong; char **cache_grl; int cache_grc; struct cache_entry *cache_next; }; static struct cache_entry *cache_head; #define VALID_TIME 60 /* seconds */ static rwlock_t cache_lock; /* protect the cache chain */ static void cache_free(struct cache_entry *entry); static int cache_check(char *host, char **grl, int grc, int *belong); static void cache_enter(char *host, char **grl, int grc, int belong); void netgroup_init() { (void) rwlock_init(&cache_lock, USYNC_THREAD, NULL); } /* * Check whether any of the hostnames in clnames are * members (or non-members) of the netgroups in glist. * Since the innetgr lookup is rather expensive, the * result is cached. The cached entry is valid only * for VALID_TIME seconds. This works well because * typically these lookups occur in clusters when * a client is mounting. * * Note that this routine establishes a host membership * in a list of netgroups - we've no idea just which * netgroup in the list it is a member of. * * glist is a character array containing grc strings * representing netgroup names (optionally prefixed * with '-'). Each string is ended with '\0' and * followed immediately by the next string. */ int netgroup_check(struct nd_hostservlist *clnames, char *glist, int grc) { char **grl; char *gr; int nhosts = clnames->h_cnt; char *host0, *host; int i, j, n; int response; int belong = 0; static char *domain; if (domain == NULL) { int ssize; domain = exmalloc(SYS_NMLN); ssize = sysinfo(SI_SRPC_DOMAIN, domain, SYS_NMLN); if (ssize > SYS_NMLN) { free(domain); domain = exmalloc(ssize); ssize = sysinfo(SI_SRPC_DOMAIN, domain, ssize); } /* Check for error in syscall or NULL domain name */ if (ssize <= 1) { syslog(LOG_ERR, "No default domain set"); return (0); } } grl = calloc(grc, sizeof (char *)); if (grl == NULL) return (0); for (i = 0, gr = glist; i < grc && !belong; ) { /* * If the netgroup name has a '-' prepended * then a match of this name implies a failure * instead of success. */ response = (*gr != '-') ? 1 : 0; /* * Subsequent names with or without a '-' (but no mix) * can be grouped together for a single check. */ for (n = 0; i < grc; i++, n++, gr += strlen(gr) + 1) { if ((response && *gr == '-') || (!response && *gr != '-')) break; grl[n] = response ? gr : gr + 1; } host0 = clnames->h_hostservs[0].h_host; /* * If not in cache check the netgroup for each * of the hosts names (usually just one). * Enter the result into the cache. */ if (!cache_check(host0, grl, n, &belong)) { for (j = 0; j < nhosts && !belong; j++) { host = clnames->h_hostservs[j].h_host; if (__multi_innetgr(n, grl, 1, &host, 0, NULL, 1, &domain)) belong = 1; } cache_enter(host0, grl, n, belong); } } free(grl); return (belong ? response : 0); } /* * Free a cache entry and all entries * further down the chain since they * will also be expired. */ static void cache_free(struct cache_entry *entry) { struct cache_entry *ce, *next; int i; for (ce = entry; ce; ce = next) { if (ce->cache_host) free(ce->cache_host); for (i = 0; i < ce->cache_grc; i++) if (ce->cache_grl[i]) free(ce->cache_grl[i]); if (ce->cache_grl) free(ce->cache_grl); next = ce->cache_next; free(ce); } } /* * Search the entries in the cache chain looking * for an entry with a matching hostname and group * list. If a match is found then return the "belong" * value which may be 1 or 0 depending on whether the * client is a member of the list or not. This is * both a positive and negative cache. * * Cache entries have a validity of VALID_TIME seconds. * If we find an expired entry then blow away the entry * and the rest of the chain since entries further down * the chain will be expired too because we always add * new entries to the head of the chain. */ static int cache_check(char *host, char **grl, int grc, int *belong) { struct cache_entry *ce, *prev; time_t timenow = time(NULL); int i; (void) rw_rdlock(&cache_lock); for (ce = cache_head; ce; ce = ce->cache_next) { /* * If we find a stale entry, there can't * be any valid entries from here on. * Acquire a write lock, search the chain again * and delete the stale entry and all following * entries. */ if (timenow > ce->cache_time) { (void) rw_unlock(&cache_lock); (void) rw_wrlock(&cache_lock); for (prev = NULL, ce = cache_head; ce; prev = ce, ce = ce->cache_next) if (timenow > ce->cache_time) break; if (ce != NULL) { if (prev) prev->cache_next = NULL; else cache_head = NULL; cache_free(ce); } (void) rw_unlock(&cache_lock); return (0); } if (ce->cache_grc != grc) continue; /* no match */ if (strcasecmp(host, ce->cache_host) != 0) continue; /* no match */ for (i = 0; i < grc; i++) if (strcasecmp(ce->cache_grl[i], grl[i]) != 0) break; /* no match */ if (i < grc) continue; *belong = ce->cache_belong; (void) rw_unlock(&cache_lock); return (1); } (void) rw_unlock(&cache_lock); return (0); } /* * Put a new entry in the cache chain by * prepending it to the front. * If there isn't enough memory then just give up. */ static void cache_enter(char *host, char **grl, int grc, int belong) { struct cache_entry *entry; int i; entry = malloc(sizeof (*entry)); if (entry == NULL) return; (void) memset((caddr_t)entry, 0, sizeof (*entry)); entry->cache_host = strdup(host); if (entry->cache_host == NULL) { cache_free(entry); return; } entry->cache_time = time(NULL) + VALID_TIME; entry->cache_belong = belong; entry->cache_grl = malloc(grc * sizeof (char *)); if (entry->cache_grl == NULL) { cache_free(entry); return; } for (i = 0; i < grc; i++) { entry->cache_grl[i] = strdup(grl[i]); if (entry->cache_grl[i] == NULL) { entry->cache_grc = i; cache_free(entry); return; } } entry->cache_grc = grc; (void) rw_wrlock(&cache_lock); entry->cache_next = cache_head; cache_head = entry; (void) rw_unlock(&cache_lock); } /* * Full cache flush */ void netgrp_cache_flush(void) { (void) rw_wrlock(&cache_lock); cache_free(cache_head); cache_head = NULL; (void) rw_unlock(&cache_lock); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2015 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../lib/sharetab.h" #include "mountd.h" /* * The following codesets must match what is in libshare_nfs.c until we can * request them from the kernel. */ char *charopts[] = { "euc-cn", "euc-jp", "euc-jpms", "euc-kr", "euc-tw", "iso8859-1", "iso8859-2", "iso8859-5", "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso8859-13", "iso8859-15", "koi8-r", NULL }; /* * nfscmd_err(dp, args, err) * Return an error for the door call. */ static void nfscmd_err(door_desc_t *dp, nfscmd_arg_t *args, int err) { nfscmd_res_t res; res.version = NFSCMD_VERS_1; res.cmd = NFSCMD_ERROR; res.error = err; (void) door_return((char *)&res, sizeof (nfscmd_res_t), NULL, 0); (void) door_return(NULL, 0, NULL, 0); /* NOTREACHED */ } /* * charmap_search(netbuf, opts) * * Check to see if the address in the netbuf is found in * a character map spec in the opts option string. Returns the charset * name if found. */ static char * charmap_search(struct netbuf *nbuf, char *opts) { char *copts; char *next; char *name; char *result = NULL; char *netid; struct sockaddr *sa; struct cln cln; sa = (struct sockaddr *)nbuf->buf; switch (sa->sa_family) { case AF_INET: netid = "tcp"; break; case AF_INET6: netid = "tcp6"; break; default: return (NULL); } copts = strdup(opts); if (copts == NULL) return (NULL); cln_init_lazy(&cln, netid, nbuf); next = copts; while (*next != '\0') { char *val; name = next; if (getsubopt(&next, charopts, &val) >= 0) { char *cp; /* * name will have the whole opt and val the value. Set * the '=' to '\0' and we have the charmap in name and * the access list in val. */ cp = strchr(name, '='); if (cp != NULL) *cp = '\0'; if (in_access_list(&cln, val) > 0) { result = name; break; } } } if (result != NULL) result = strdup(result); cln_fini(&cln); free(copts); return (result); } /* * nfscmd_charmap_lookup(door, args) * * Check to see if there is a translation requested for the path * specified in the request. If there is, return the charset name. */ static void nfscmd_charmap_lookup(door_desc_t *dp, nfscmd_arg_t *args) { nfscmd_res_t res; struct netbuf nb; struct sockaddr sa; struct share *sh = NULL; char *name; memset(&res, '\0', sizeof (res)); res.version = NFSCMD_VERS_1; res.cmd = NFSCMD_CHARMAP_LOOKUP; sh = findentry(args->arg.charmap.path); if (sh != NULL) { nb.len = nb.maxlen = sizeof (struct sockaddr); nb.buf = (char *)&sa; sa = args->arg.charmap.addr; name = charmap_search(&nb, sh->sh_opts); if (name != NULL) { (void) strcpy(res.result.charmap.codeset, name); res.result.charmap.apply = B_TRUE; res.error = NFSCMD_ERR_SUCCESS; free(name); } else { res.result.charmap.apply = B_FALSE; res.error = NFSCMD_ERR_NOTFOUND; } sharefree(sh); } else { res.error = NFSCMD_ERR_NOTFOUND; } (void) door_return((char *)&res, sizeof (nfscmd_res_t), NULL, 0); (void) door_return(NULL, 0, NULL, 0); /* NOTREACHED */ } /* * nfscmd_ver_1(door, args, size) * * Version 1 of the door command processor for nfs cmds. */ static void nfscmd_vers_1(door_desc_t *dp, nfscmd_arg_t *args, size_t size) { switch (args->cmd) { case NFSCMD_CHARMAP_LOOKUP: nfscmd_charmap_lookup(dp, args); break; default: nfscmd_err(dp, args, NFSCMD_ERR_BADCMD); break; } } /* * nfscmd_func(cookie, dataptr, size, door, ndesc) * * The function called by the door thread for processing * nfscmd type commands. */ void nfscmd_func(void *cookie, char *dataptr, size_t arg_size, door_desc_t *dp, uint_t n_desc) { nfscmd_arg_t *args; args = (nfscmd_arg_t *)dataptr; switch (args->version) { case NFSCMD_VERS_1: nfscmd_vers_1(dp, args, arg_size); break; default: syslog(LOG_ERR, gettext("Invalid nfscmd version")); break; } (void) door_return((caddr_t)args, sizeof (nfscmd_res_t), NULL, 0); (void) door_return(NULL, 0, NULL, 0); /* NOTREACHED */ } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../lib/sharetab.h" #include "mountd.h" static void nfsauth_access(auth_req *argp, auth_res *result) { struct netbuf nbuf; struct share *sh; struct cln cln; result->auth_perm = NFSAUTH_DENIED; nbuf.len = argp->req_client.n_len; nbuf.buf = argp->req_client.n_bytes; if (nbuf.len == 0 || nbuf.buf == NULL) return; /* * Find the export */ sh = findentry(argp->req_path); if (sh == NULL) { syslog(LOG_ERR, "%s not exported", argp->req_path); return; } cln_init_lazy(&cln, argp->req_netid, &nbuf); result->auth_perm = check_client(sh, &cln, argp->req_flavor, argp->req_clnt_uid, argp->req_clnt_gid, argp->req_clnt_gids.len, argp->req_clnt_gids.val, &result->auth_srv_uid, &result->auth_srv_gid, &result->auth_srv_gids.len, &result->auth_srv_gids.val); sharefree(sh); if (result->auth_perm == NFSAUTH_DENIED) { char *host = cln_gethost(&cln); if (host != NULL) syslog(LOG_ERR, "%s denied access to %s", host, argp->req_path); } cln_fini(&cln); } void nfsauth_func(void *cookie, char *dataptr, size_t arg_size, door_desc_t *dp, uint_t n_desc) { nfsauth_arg_t *ap; nfsauth_res_t res = {0}; XDR xdrs_a; XDR xdrs_r; size_t rbsz; caddr_t rbuf; varg_t varg = {0}; /* * Decode the inbound door data, so we can look at the cmd. */ xdrmem_create(&xdrs_a, dataptr, arg_size, XDR_DECODE); if (!xdr_varg(&xdrs_a, &varg)) { /* * If the arguments can't be decoded, bail. */ if (varg.vers == V_ERROR) syslog(LOG_ERR, gettext("Arg version mismatch")); res.stat = NFSAUTH_DR_DECERR; goto encres; } /* * Now set the args pointer to the proper version of the args */ switch (varg.vers) { case V_PROTO: ap = &varg.arg_u.arg; break; /* Additional arguments versions go here */ default: syslog(LOG_ERR, gettext("Invalid args version")); res.stat = NFSAUTH_DR_DECERR; goto encres; } /* * Call the specified cmd */ switch (ap->cmd) { case NFSAUTH_ACCESS: nfsauth_access(&ap->areq, &res.ares); res.stat = NFSAUTH_DR_OKAY; break; default: res.stat = NFSAUTH_DR_BADCMD; break; } encres: /* * Free space used to decode the args */ xdr_free(xdr_varg, (char *)&varg); xdr_destroy(&xdrs_a); /* * Encode the results before passing thru door. */ rbsz = xdr_sizeof(xdr_nfsauth_res, &res); if (rbsz == 0) goto failed; rbuf = alloca(rbsz); xdrmem_create(&xdrs_r, rbuf, rbsz, XDR_ENCODE); if (!xdr_nfsauth_res(&xdrs_r, &res)) { xdr_destroy(&xdrs_r); failed: xdr_free(xdr_nfsauth_res, (char *)&res); /* * return only the status code */ res.stat = NFSAUTH_DR_EFAIL; rbsz = sizeof (uint_t); rbuf = (caddr_t)&res.stat; goto out; } xdr_destroy(&xdrs_r); xdr_free(xdr_nfsauth_res, (char *)&res); out: (void) door_return((char *)rbuf, rbsz, NULL, 0); (void) door_return(NULL, 0, NULL, 0); /* NOTREACHED */ } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include bool_t xdr_varg(XDR *xdrs, varg_t *vap) { if (!xdr_u_int(xdrs, &vap->vers)) return (FALSE); switch (vap->vers) { case V_PROTO: if (!xdr_nfsauth_arg(xdrs, &vap->arg_u.arg)) return (FALSE); break; /* Additional versions of the args go here */ default: vap->vers = V_ERROR; return (FALSE); /* NOTREACHED */ } return (TRUE); } bool_t xdr_nfsauth_arg(XDR *xdrs, nfsauth_arg_t *argp) { if (!xdr_u_int(xdrs, &argp->cmd)) return (FALSE); if (!xdr_netobj(xdrs, &argp->areq.req_client)) return (FALSE); if (!xdr_string(xdrs, &argp->areq.req_netid, ~0)) return (FALSE); if (!xdr_string(xdrs, &argp->areq.req_path, A_MAXPATH)) return (FALSE); if (!xdr_int(xdrs, &argp->areq.req_flavor)) return (FALSE); if (!xdr_uid_t(xdrs, &argp->areq.req_clnt_uid)) return (FALSE); if (!xdr_gid_t(xdrs, &argp->areq.req_clnt_gid)) return (FALSE); if (!xdr_array(xdrs, (caddr_t *)&argp->areq.req_clnt_gids.val, &argp->areq.req_clnt_gids.len, NGROUPS_UMAX, (uint_t)sizeof (gid_t), xdr_gid_t)) return (FALSE); return (TRUE); } bool_t xdr_nfsauth_res(XDR *xdrs, nfsauth_res_t *argp) { if (!xdr_u_int(xdrs, &argp->stat)) return (FALSE); if (!xdr_int(xdrs, &argp->ares.auth_perm)) return (FALSE); if (!xdr_uid_t(xdrs, &argp->ares.auth_srv_uid)) return (FALSE); if (!xdr_gid_t(xdrs, &argp->ares.auth_srv_gid)) return (FALSE); if (!xdr_array(xdrs, (caddr_t *)&argp->ares.auth_srv_gids.val, &argp->ares.auth_srv_gids.len, NGROUPS_UMAX, (uint_t)sizeof (gid_t), xdr_gid_t)) return (FALSE); return (TRUE); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../lib/sharetab.h" #include "hashset.h" #include "mountd.h" static char RMTAB[] = "/etc/rmtab"; static FILE *rmtabf = NULL; /* * There is nothing magic about the value selected here. Too low, * and mountd might spend too much time rewriting the rmtab file. * Too high, it won't do it frequently enough. */ static int rmtab_del_thresh = 250; #define RMTAB_TOOMANY_DELETED() \ ((rmtab_deleted > rmtab_del_thresh) && (rmtab_deleted > rmtab_inuse)) /* * mountd's version of a "struct mountlist". It is the same except * for the added ml_pos field. */ struct mntentry { char *m_host; char *m_path; long m_pos; }; static HASHSET mntlist; static int mntentry_equal(const void *, const void *); static uint32_t mntentry_hash(const void *); static int mntlist_contains(char *, char *); static void rmtab_delete(long); static long rmtab_insert(char *, char *); static void rmtab_rewrite(void); static void rmtab_parse(char *buf); static bool_t xdr_mntlistencode(XDR * xdrs, HASHSET * mntlist); #define exstrdup(s) \ strcpy(exmalloc(strlen(s)+1), s) static int rmtab_inuse; static int rmtab_deleted; static rwlock_t rmtab_lock; /* lock to protect rmtab list */ /* * Check whether the given client/path combination * already appears in the mount list. */ static int mntlist_contains(char *host, char *path) { struct mntentry m; m.m_host = host; m.m_path = path; return (h_get(mntlist, &m) != NULL); } /* * Add an entry to the mount list. * First check whether it's there already - the client * may have crashed and be rebooting. */ static void mntlist_insert(char *host, char *path) { if (!mntlist_contains(host, path)) { struct mntentry *m; m = exmalloc(sizeof (struct mntentry)); m->m_host = exstrdup(host); m->m_path = exstrdup(path); m->m_pos = rmtab_insert(host, path); (void) h_put(mntlist, m); } } void mntlist_new(char *host, char *path) { (void) rw_wrlock(&rmtab_lock); mntlist_insert(host, path); (void) rw_unlock(&rmtab_lock); } /* * Delete an entry from the mount list. */ void mntlist_delete(char *host, char *path) { struct mntentry *m, mm; mm.m_host = host; mm.m_path = path; (void) rw_wrlock(&rmtab_lock); if ((m = (struct mntentry *)h_get(mntlist, &mm)) != NULL) { rmtab_delete(m->m_pos); (void) h_delete(mntlist, m); free(m->m_path); free(m->m_host); free(m); if (RMTAB_TOOMANY_DELETED()) rmtab_rewrite(); } (void) rw_unlock(&rmtab_lock); } /* * Delete all entries for a host from the mount list */ void mntlist_delete_all(char *host) { HASHSET_ITERATOR iterator; struct mntentry *m; (void) rw_wrlock(&rmtab_lock); iterator = h_iterator(mntlist); while ((m = (struct mntentry *)h_next(iterator)) != NULL) { if (strcasecmp(m->m_host, host)) continue; rmtab_delete(m->m_pos); (void) h_delete(mntlist, m); free(m->m_path); free(m->m_host); free(m); } if (RMTAB_TOOMANY_DELETED()) rmtab_rewrite(); (void) rw_unlock(&rmtab_lock); if (iterator != NULL) free(iterator); } /* * Equivalent to xdr_mountlist from librpcsvc but for HASHSET * rather that for a linked list. It is used only to encode data * from HASHSET before sending it over the wire. */ static bool_t xdr_mntlistencode(XDR *xdrs, HASHSET *mntlist) { HASHSET_ITERATOR iterator = h_iterator(*mntlist); for (;;) { struct mntentry *m = (struct mntentry *)h_next(iterator); bool_t more_data = (m != NULL); if (!xdr_bool(xdrs, &more_data)) { if (iterator != NULL) free(iterator); return (FALSE); } if (!more_data) break; if ((!xdr_name(xdrs, &m->m_host)) || (!xdr_dirpath(xdrs, &m->m_path))) { if (iterator != NULL) free(iterator); return (FALSE); } } if (iterator != NULL) free(iterator); return (TRUE); } void mntlist_send(SVCXPRT *transp) { (void) rw_rdlock(&rmtab_lock); errno = 0; if (!svc_sendreply(transp, xdr_mntlistencode, (char *)&mntlist)) log_cant_reply(transp); (void) rw_unlock(&rmtab_lock); } /* * Compute a 32 bit hash value for an mntlist entry. */ /* * The string hashing algorithm is from the "Dragon Book" -- * "Compilers: Principles, Tools & Techniques", by Aho, Sethi, Ullman * * And is modified for this application from usr/src/uts/common/os/modhash.c */ static uint_t mntentry_str_hash(char *s, uint_t hash) { uint_t g; for (; *s != '\0'; s++) { hash = (hash << 4) + *s; if ((g = (hash & 0xf0000000)) != 0) { hash ^= (g >> 24); hash ^= g; } } return (hash); } static uint32_t mntentry_hash(const void *p) { struct mntentry *m = (struct mntentry *)p; uint_t hash; hash = mntentry_str_hash(m->m_host, 0); hash = mntentry_str_hash(m->m_path, hash); return (hash); } /* * Compare mntlist entries. * The comparison ignores a value of m_pos. */ static int mntentry_equal(const void *p1, const void *p2) { struct mntentry *m1 = (struct mntentry *)p1; struct mntentry *m2 = (struct mntentry *)p2; return ((strcasecmp(m1->m_host, m2->m_host) || strcmp(m1->m_path, m2->m_path)) ? 0 : 1); } /* * Rewrite /etc/rmtab with a current content of mntlist. */ static void rmtab_rewrite() { if (rmtabf) (void) fclose(rmtabf); /* Rewrite the file. */ if ((rmtabf = fopen(RMTAB, "w+")) != NULL) { HASHSET_ITERATOR iterator; struct mntentry *m; (void) fchmod(fileno(rmtabf), (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)); rmtab_inuse = rmtab_deleted = 0; iterator = h_iterator(mntlist); while ((m = (struct mntentry *)h_next(iterator)) != NULL) m->m_pos = rmtab_insert(m->m_host, m->m_path); if (iterator != NULL) free(iterator); } } /* * Parse the content of /etc/rmtab and insert the entries into mntlist. * The buffer s should be ended with a NUL char. */ static void rmtab_parse(char *s) { char *host; char *path; char *tmp; struct in6_addr ipv6addr; host_part: if (*s == '#') goto skip_rest; host = s; for (;;) { switch (*s++) { case '\0': return; case '\n': goto host_part; case ':': s[-1] = '\0'; goto path_part; case '[': tmp = strchr(s, ']'); if (tmp) { *tmp = '\0'; if (inet_pton(AF_INET6, s, &ipv6addr) > 0) { host = s; s = ++tmp; } else *tmp = ']'; } /* FALLTHROUGH */ default: continue; } } path_part: path = s; for (;;) { switch (*s++) { case '\n': s[-1] = '\0'; if (*host && *path) mntlist_insert(host, path); goto host_part; case '\0': if (*host && *path) mntlist_insert(host, path); return; default: continue; } } skip_rest: for (;;) { switch (*++s) { case '\n': goto host_part; case '\0': return; default: continue; } } } /* * Read in contents of rmtab. * Call rmtab_parse to parse the file and store entries in mntlist. * Rewrites the file to get rid of unused entries. */ #define RMTAB_LOADLEN (16*2024) /* Max bytes to read at a time */ void rmtab_load() { FILE *fp; (void) rwlock_init(&rmtab_lock, USYNC_THREAD, NULL); /* * Don't need to lock the list at this point * because there's only a single thread running. */ mntlist = h_create(mntentry_hash, mntentry_equal, 101, 0.75); if ((fp = fopen(RMTAB, "r")) != NULL) { char buf[RMTAB_LOADLEN+1]; size_t len; /* * Read at most RMTAB_LOADLEN bytes from /etc/rmtab. * - if fread returns RMTAB_LOADLEN we can be in the middle * of a line so change the last newline character into NUL * and seek back to the next character after newline. * - otherwise set NUL behind the last character read. */ while ((len = fread(buf, 1, RMTAB_LOADLEN, fp)) > 0) { if (len == RMTAB_LOADLEN) { int i; for (i = 1; i < len; i++) { if (buf[len-i] == '\n') { buf[len-i] = '\0'; (void) fseek(fp, -i + 1, SEEK_CUR); goto parse; } } } /* Put a NUL character at the end of buffer */ buf[len] = '\0'; parse: rmtab_parse(buf); } (void) fclose(fp); } rmtab_rewrite(); } /* * Write an entry at the current location in rmtab * for the given client and path. * * Returns the starting position of the entry * or -1 if there was an error. */ long rmtab_insert(char *host, char *path) { long pos; struct in6_addr ipv6addr; if (rmtabf == NULL || fseek(rmtabf, 0L, 2) == -1) { return (-1); } pos = ftell(rmtabf); /* * Check if host is an IPv6 literal */ if (inet_pton(AF_INET6, host, &ipv6addr) > 0) { if (fprintf(rmtabf, "[%s]:%s\n", host, path) == EOF) { return (-1); } } else { if (fprintf(rmtabf, "%s:%s\n", host, path) == EOF) { return (-1); } } if (fflush(rmtabf) == EOF) { return (-1); } rmtab_inuse++; return (pos); } /* * Mark as unused the rmtab entry at the given offset in the file. */ void rmtab_delete(long pos) { if (rmtabf != NULL && pos != -1 && fseek(rmtabf, pos, 0) == 0) { (void) fprintf(rmtabf, "#"); (void) fflush(rmtabf); rmtab_inuse--; rmtab_deleted++; } }