static share_t * sharedup(share_t *sh) { share_t *nsh; nsh = (share_t *)calloc(1, sizeof (*nsh)); if (nsh == NULL) return (NULL); nsh->sh_path = strdup(sh->sh_path); if (nsh->sh_path == NULL) goto alloc_failed; nsh->sh_res = strdup(sh->sh_res); if (nsh->sh_res == NULL) goto alloc_failed; nsh->sh_fstype = strdup(sh->sh_fstype); if (nsh->sh_fstype == NULL) goto alloc_failed; nsh->sh_opts = strdup(sh->sh_opts); if (nsh->sh_opts == NULL) goto alloc_failed; nsh->sh_descr = strdup(sh->sh_descr); if (nsh->sh_descr == NULL) goto alloc_failed; return (nsh); alloc_failed: sharefree(nsh); return (NULL); }
static void sh_free(struct sh_list *shp) { register struct sh_list *next; while (shp) { sharefree(shp->shl_sh); next = shp->shl_next; free(shp); shp = next; } }
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 *opts; 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) { 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 */ }
/* * Check mount requests, add to mounted list if ok */ static void 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]; struct share *sh = NULL; struct nd_hostservlist *clnames = NULL; char *host = NULL; int error = 0, lofs_tried = 0; int flavor_list[MAX_FLAVORS]; int flavor_count; struct netbuf *nb; transp = rqstp->rq_xprt; version = rqstp->rq_vers; path = NULL; if (!svc_getargs(transp, xdr_dirpath, (caddr_t)&path)) { svcerr_decode(transp); return; } getclientsnames(transp, &nb, &clnames); if (clnames == NULL || nb == 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; } host = clnames->h_hostservs[0].h_host; /* * 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 older versions of nfs(v2, v3). * To prevent circumventing TX label policy via using an older * version of nfs client, reject the mount request and log an * error. */ if (is_system_labeled()) { syslog(LOG_ERR, "mount rejected: Solaris TX only supports nfs4 clients"); 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(path, rpath, clnames, nb, 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, nb, clnames, flavor_list); else flavor_count = getclientsflavors_old(sh, nb, clnames, flavor_list); if (flavor_count == 0) { error = EACCES; goto reply; } /* * 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: 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(transp); audit_mountd_mount(host, path, fhs.fhs_status); /* BSM */ 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(transp); audit_mountd_mount(host, path, mountres3.fhs_status); /* BSM */ break; } if (verbose) syslog(LOG_NOTICE, "MOUNT: %s %s %s", (host == NULL) ? "unknown host" : host, error ? "denied" : "mounted", path); if (path != NULL) svc_freeargs(transp, xdr_dirpath, (caddr_t)&path); if (!error) mntlist_new(host, rpath); /* add entry to mount list */ done: if (sh) sharefree(sh); netdir_free(clnames, ND_HOSTSERVLIST); }
/* * 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(char *path, char *rpath, struct nd_hostservlist *clnames, struct netbuf *nb, int *flavor_list) { char *checkpath, *dp; struct share *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, nb, clnames, flavor_list); else flavor_count = getclientsflavors_old(sh, nb, clnames, 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); }
/* * 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 struct share * find_lofsentry(char *rpath, int *done_flag) { struct stat r_stbuf; mntlist_t *ml, *mntl, *mntpnt = NULL; struct share *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); }
static void nfsauth_access(auth_req *argp, auth_res *result) { struct netconfig *nconf; struct nd_hostservlist *clnames = NULL; struct netbuf nbuf; struct share *sh; char tmp[MAXIPADDRLEN]; char *host = NULL; result->auth_perm = NFSAUTH_DENIED; /* * Convert the client's address to a hostname */ nconf = getnetconfigent(argp->req_netid); if (nconf == NULL) { syslog(LOG_ERR, "No netconfig entry for %s", argp->req_netid); return; } nbuf.len = argp->req_client.n_len; nbuf.buf = argp->req_client.n_bytes; if (netdir_getbyaddr(nconf, &clnames, &nbuf)) { host = &tmp[0]; if (strcmp(nconf->nc_protofmly, NC_INET) == 0) { struct sockaddr_in *sa; /* LINTED pointer alignment */ sa = (struct sockaddr_in *)nbuf.buf; (void) inet_ntoa_r(sa->sin_addr, tmp); } else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) { struct sockaddr_in6 *sa; /* LINTED pointer */ sa = (struct sockaddr_in6 *)nbuf.buf; (void) inet_ntop(AF_INET6, sa->sin6_addr.s6_addr, tmp, INET6_ADDRSTRLEN); } clnames = anon_client(host); } /* * Now find the export */ sh = findentry(argp->req_path); if (sh == NULL) { syslog(LOG_ERR, "%s not exported", argp->req_path); goto done; } result->auth_perm = check_client(sh, &nbuf, clnames, argp->req_flavor); sharefree(sh); if (result->auth_perm == NFSAUTH_DENIED) { syslog(LOG_ERR, "%s denied access to %s", clnames->h_hostservs[0].h_host, argp->req_path); } done: freenetconfigent(nconf); if (clnames) netdir_free(clnames, ND_HOSTSERVLIST); }