/* * 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) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2017 Nexenta Systems, Inc. All rights reserved. * Copyright 2017 Joyent, Inc. * Copyright 2020-2023 RackTop Systems, Inc. */ #include #include #include #include typedef struct smb_unshare { list_node_t us_lnd; char us_sharename[MAXNAMELEN]; } smb_unshare_t; static kmem_cache_t *smb_kshare_cache_share; static kmem_cache_t *smb_kshare_cache_unexport; static int smb_kshare_cmp(const void *, const void *); static void smb_kshare_hold(const void *); static boolean_t smb_kshare_rele(const void *); static void smb_kshare_destroy(void *); static char *smb_kshare_oemname(const char *); static int smb_kshare_is_special(const char *); static int smb_kshare_is_admin(const char *); static smb_kshare_t *smb_kshare_decode(nvlist_t *); static uint32_t smb_kshare_decode_bool(nvlist_t *, const char *, uint32_t); static void smb_kshare_unexport_thread(smb_thread_t *, void *); static int smb_kshare_export(smb_server_t *, smb_kshare_t *); static int smb_kshare_unexport(smb_server_t *, const char *); static int smb_kshare_export_trans(smb_server_t *, char *, char *, char *); static void smb_kshare_csc_flags(smb_kshare_t *, const char *); static boolean_t smb_export_isready(smb_server_t *); #ifdef _KERNEL static int smb_kshare_chk_dsrv_status(int, smb_dr_ctx_t *); #endif /* _KERNEL */ static const smb_avl_nops_t smb_kshare_avlops = { smb_kshare_cmp, smb_kshare_hold, smb_kshare_rele, smb_kshare_destroy }; #ifdef _KERNEL /* * This function is not MultiThread safe. The caller has to make sure only one * thread calls this function. */ door_handle_t smb_kshare_door_init(int door_id) { return (door_ki_lookup(door_id)); } /* * This function is not MultiThread safe. The caller has to make sure only one * thread calls this function. */ void smb_kshare_door_fini(door_handle_t dhdl) { if (dhdl) door_ki_rele(dhdl); } /* * This is a special interface that will be utilized by ZFS to cause * a share to be added/removed * * arg is either a smb_share_t or share_name from userspace. * It will need to be copied into the kernel. It is smb_share_t * for add operations and share_name for delete operations. */ int smb_kshare_upcall(door_handle_t dhdl, void *arg, boolean_t add_share) { door_arg_t doorarg = { 0 }; char *buf = NULL; char *str = NULL; int error; int rc; unsigned int used; smb_dr_ctx_t *dec_ctx; smb_dr_ctx_t *enc_ctx; smb_share_t *lmshare = NULL; int opcode; opcode = (add_share) ? SMB_SHROP_ADD : SMB_SHROP_DELETE; buf = kmem_alloc(SMB_SHARE_DSIZE, KM_SLEEP); enc_ctx = smb_dr_encode_start(buf, SMB_SHARE_DSIZE); smb_dr_put_uint32(enc_ctx, opcode); switch (opcode) { case SMB_SHROP_ADD: lmshare = kmem_alloc(sizeof (smb_share_t), KM_SLEEP); error = xcopyin(arg, lmshare, sizeof (smb_share_t)); if (error != 0) { kmem_free(lmshare, sizeof (smb_share_t)); kmem_free(buf, SMB_SHARE_DSIZE); return (error); } smb_dr_put_share(enc_ctx, lmshare); break; case SMB_SHROP_DELETE: str = kmem_alloc(MAXPATHLEN, KM_SLEEP); error = copyinstr(arg, str, MAXPATHLEN, NULL); if (error != 0) { kmem_free(str, MAXPATHLEN); kmem_free(buf, SMB_SHARE_DSIZE); return (error); } smb_dr_put_string(enc_ctx, str); kmem_free(str, MAXPATHLEN); break; } if ((error = smb_dr_encode_finish(enc_ctx, &used)) != 0) { kmem_free(buf, SMB_SHARE_DSIZE); if (lmshare) kmem_free(lmshare, sizeof (smb_share_t)); return (NERR_InternalError); } doorarg.data_ptr = buf; doorarg.data_size = used; doorarg.rbuf = buf; doorarg.rsize = SMB_SHARE_DSIZE; error = door_ki_upcall_limited(dhdl, &doorarg, NULL, SIZE_MAX, 0); if (error) { kmem_free(buf, SMB_SHARE_DSIZE); if (lmshare) kmem_free(lmshare, sizeof (smb_share_t)); return (error); } dec_ctx = smb_dr_decode_start(doorarg.data_ptr, doorarg.data_size); if (smb_kshare_chk_dsrv_status(opcode, dec_ctx) != 0) { kmem_free(buf, SMB_SHARE_DSIZE); if (lmshare) kmem_free(lmshare, sizeof (smb_share_t)); return (NERR_InternalError); } rc = smb_dr_get_uint32(dec_ctx); if (opcode == SMB_SHROP_ADD) smb_dr_get_share(dec_ctx, lmshare); if (smb_dr_decode_finish(dec_ctx)) rc = NERR_InternalError; kmem_free(buf, SMB_SHARE_DSIZE); if (lmshare) kmem_free(lmshare, sizeof (smb_share_t)); return ((rc == NERR_DuplicateShare && add_share) ? 0 : rc); } #endif /* _KERNEL */ /* * Executes map and unmap command for shares. */ int smb_kshare_exec(smb_server_t *sv, smb_shr_execinfo_t *execinfo) { int exec_rc = 0; (void) smb_kdoor_upcall(sv, SMB_DR_SHR_EXEC, execinfo, smb_shr_execinfo_xdr, &exec_rc, xdr_int); return (exec_rc); } /* * Obtains any host access restriction on the specified * share for the given host (ipaddr) by calling smbd */ uint32_t smb_kshare_hostaccess(smb_kshare_t *shr, smb_session_t *session) { smb_shr_hostaccess_query_t req; smb_inaddr_t *ipaddr = &session->ipaddr; uint32_t host_access = SMB_SHRF_ACC_OPEN; uint32_t flag = SMB_SHRF_ACC_OPEN; uint32_t access; if (smb_inet_iszero(ipaddr)) return (ACE_ALL_PERMS); if ((shr->shr_access_none == NULL || *shr->shr_access_none == '\0') && (shr->shr_access_ro == NULL || *shr->shr_access_ro == '\0') && (shr->shr_access_rw == NULL || *shr->shr_access_rw == '\0')) return (ACE_ALL_PERMS); if (shr->shr_access_none != NULL) flag |= SMB_SHRF_ACC_NONE; if (shr->shr_access_ro != NULL) flag |= SMB_SHRF_ACC_RO; if (shr->shr_access_rw != NULL) flag |= SMB_SHRF_ACC_RW; req.shq_none = shr->shr_access_none; req.shq_ro = shr->shr_access_ro; req.shq_rw = shr->shr_access_rw; req.shq_flag = flag; req.shq_ipaddr = *ipaddr; (void) smb_kdoor_upcall(session->s_server, SMB_DR_SHR_HOSTACCESS, &req, smb_shr_hostaccess_query_xdr, &host_access, xdr_uint32_t); switch (host_access) { case SMB_SHRF_ACC_RO: access = ACE_ALL_PERMS & ~ACE_ALL_WRITE_PERMS; break; case SMB_SHRF_ACC_OPEN: case SMB_SHRF_ACC_RW: access = ACE_ALL_PERMS; break; case SMB_SHRF_ACC_NONE: default: access = 0; } return (access); } /* * This function is called when smb_server_t is * created which means smb/service is ready for * exporting SMB shares */ void smb_export_start(smb_server_t *sv) { mutex_enter(&sv->sv_export.e_mutex); if (sv->sv_export.e_ready) { mutex_exit(&sv->sv_export.e_mutex); return; } sv->sv_export.e_ready = B_TRUE; mutex_exit(&sv->sv_export.e_mutex); smb_avl_create(&sv->sv_export.e_share_avl, sizeof (smb_kshare_t), offsetof(smb_kshare_t, shr_link), &smb_kshare_avlops); (void) smb_kshare_export_trans(sv, "IPC$", "IPC$", "Remote IPC"); (void) smb_kshare_export_trans(sv, "c$", SMB_CVOL, "Default Share"); (void) smb_kshare_export_trans(sv, "vss$", SMB_VSS, "VSS"); } /* * This function is called when smb_server_t goes * away which means SMB shares should not be made * available to clients */ void smb_export_stop(smb_server_t *sv) { mutex_enter(&sv->sv_export.e_mutex); if (!sv->sv_export.e_ready) { mutex_exit(&sv->sv_export.e_mutex); return; } sv->sv_export.e_ready = B_FALSE; mutex_exit(&sv->sv_export.e_mutex); smb_avl_destroy(&sv->sv_export.e_share_avl); } void smb_kshare_g_init(void) { smb_kshare_cache_share = kmem_cache_create("smb_share_cache", sizeof (smb_kshare_t), 8, NULL, NULL, NULL, NULL, NULL, 0); smb_kshare_cache_unexport = kmem_cache_create("smb_unexport_cache", sizeof (smb_unshare_t), 8, NULL, NULL, NULL, NULL, NULL, 0); } void smb_kshare_init(smb_server_t *sv) { smb_slist_constructor(&sv->sv_export.e_unexport_list, sizeof (smb_unshare_t), offsetof(smb_unshare_t, us_lnd)); } int smb_kshare_start(smb_server_t *sv) { smb_thread_init(&sv->sv_export.e_unexport_thread, "smb_kshare_unexport", smb_kshare_unexport_thread, sv, smbsrv_base_pri, sv); return (smb_thread_start(&sv->sv_export.e_unexport_thread)); } void smb_kshare_stop(smb_server_t *sv) { smb_thread_stop(&sv->sv_export.e_unexport_thread); smb_thread_destroy(&sv->sv_export.e_unexport_thread); } void smb_kshare_fini(smb_server_t *sv) { smb_unshare_t *ux; while ((ux = list_head(&sv->sv_export.e_unexport_list.sl_list)) != NULL) { smb_slist_remove(&sv->sv_export.e_unexport_list, ux); kmem_cache_free(smb_kshare_cache_unexport, ux); } smb_slist_destructor(&sv->sv_export.e_unexport_list); } void smb_kshare_g_fini(void) { kmem_cache_destroy(smb_kshare_cache_unexport); kmem_cache_destroy(smb_kshare_cache_share); } /* * A list of shares in nvlist format can be sent down * from userspace thourgh the IOCTL interface. The nvlist * is unpacked here and all the shares in the list will * be exported. */ int smb_kshare_export_list(smb_server_t *sv, smb_ioc_share_t *ioc) { nvlist_t *shrlist = NULL; nvlist_t *share; nvpair_t *nvp; smb_kshare_t *shr; char *shrname; int rc; if (!smb_export_isready(sv)) { rc = ENOTACTIVE; goto out; } /* * Reality check that the nvlist's reported length doesn't exceed the * ioctl's total length. We then assume the nvlist_unpack() will * sanity check the nvlist itself. */ if ((ioc->shrlen + offsetof(smb_ioc_share_t, shr)) > ioc->hdr.len) { rc = EINVAL; goto out; } rc = nvlist_unpack(ioc->shr, ioc->shrlen, &shrlist, KM_SLEEP); if (rc != 0) goto out; for (nvp = nvlist_next_nvpair(shrlist, NULL); nvp != NULL; nvp = nvlist_next_nvpair(shrlist, nvp)) { /* * Since this loop can run for a while we want to exit * as soon as the server state is anything but RUNNING * to allow shutdown to proceed. */ if (sv->sv_state != SMB_SERVER_STATE_RUNNING) goto out; if (nvpair_type(nvp) != DATA_TYPE_NVLIST) continue; shrname = nvpair_name(nvp); ASSERT(shrname); if ((rc = nvpair_value_nvlist(nvp, &share)) != 0) { cmn_err(CE_WARN, "export[%s]: failed accessing", shrname); continue; } if ((shr = smb_kshare_decode(share)) == NULL) { cmn_err(CE_WARN, "export[%s]: failed decoding", shrname); continue; } /* smb_kshare_export consumes shr so it's not leaked */ if ((rc = smb_kshare_export(sv, shr)) != 0) { smb_kshare_destroy(shr); continue; } } rc = 0; out: nvlist_free(shrlist); return (rc); } /* * This function is invoked when a share is disabled to disconnect trees * and close files. Cleaning up may involve VOP and/or VFS calls, which * may conflict/deadlock with stuck threads if something is amiss with the * file system. Queueing the request for asynchronous processing allows the * call to return immediately so that, if the unshare is being done in the * context of a forced unmount, the forced unmount will always be able to * proceed (unblocking stuck I/O and eventually allowing all blocked unshare * processes to complete). * * The path lookup to find the root vnode of the VFS in question and the * release of this vnode are done synchronously prior to any associated * unmount. Doing these asynchronous to an associated unmount could run * the risk of a spurious EBUSY for a standard unmount or an EIO during * the path lookup due to a forced unmount finishing first. */ int smb_kshare_unexport_list(smb_server_t *sv, smb_ioc_share_t *ioc) { smb_unshare_t *ux; nvlist_t *shrlist = NULL; nvpair_t *nvp; boolean_t unexport = B_FALSE; char *shrname; int rc; /* * Reality check that the nvlist's reported length doesn't exceed the * ioctl's total length. We then assume the nvlist_unpack() will * sanity check the nvlist itself. */ if ((ioc->shrlen + offsetof(smb_ioc_share_t, shr)) > ioc->hdr.len) { rc = EINVAL; goto out; } if ((rc = nvlist_unpack(ioc->shr, ioc->shrlen, &shrlist, 0)) != 0) goto out; for (nvp = nvlist_next_nvpair(shrlist, NULL); nvp != NULL; nvp = nvlist_next_nvpair(shrlist, nvp)) { if (nvpair_type(nvp) != DATA_TYPE_NVLIST) continue; shrname = nvpair_name(nvp); ASSERT(shrname); if ((rc = smb_kshare_unexport(sv, shrname)) != 0) continue; ux = kmem_cache_alloc(smb_kshare_cache_unexport, KM_SLEEP); (void) strlcpy(ux->us_sharename, shrname, MAXNAMELEN); smb_slist_insert_tail(&sv->sv_export.e_unexport_list, ux); unexport = B_TRUE; } if (unexport) smb_thread_signal(&sv->sv_export.e_unexport_thread); rc = 0; out: nvlist_free(shrlist); return (rc); } /* * Get properties (currently only shortname enablement) * of specified share. */ int smb_kshare_info(smb_server_t *sv, smb_ioc_shareinfo_t *ioc) { ioc->shortnames = sv->sv_cfg.skc_short_names; return (0); } /* * smb_kshare_access * * Does this user have access to the share? * returns: 0 (access OK) or errno * * SMB users always have VEXEC (traverse) via privileges, * so just check for READ or WRITE permissions. */ int smb_kshare_access(smb_server_t *sv, smb_ioc_shareaccess_t *ioc) { smb_user_t *user = NULL; smb_kshare_t *shr = NULL; smb_node_t *shroot = NULL; vnode_t *vp = NULL; int rc = EACCES; shr = smb_kshare_lookup(sv, ioc->shrname); if (shr == NULL) { rc = ENOENT; goto out; } if ((shroot = shr->shr_root_node) == NULL) { /* Only "file" shares have shr_root_node */ rc = 0; goto out; } vp = shroot->vp; user = smb_server_lookup_user(sv, ioc->session_id, ioc->user_id); if (user == NULL) { rc = EINVAL; goto out; } ASSERT(user->u_cred != NULL); rc = smb_vop_access(vp, VREAD, 0, NULL, user->u_cred); if (rc != 0) rc = smb_vop_access(vp, VWRITE, 0, NULL, user->u_cred); out: if (user != NULL) smb_user_release(user); if (shr != NULL) smb_kshare_release(sv, shr); return (rc); } /* * This function builds a response for a NetShareEnum RAP request. * List of shares is scanned twice. In the first round the total number * of shares which their OEM name is shorter than 13 chars (esi->es_ntotal) * and also the number of shares that fit in the given buffer are calculated. * In the second round the shares data are encoded in the buffer. * * The data associated with each share has two parts, a fixed size part and * a variable size part which is share's comment. The outline of the response * buffer is so that fixed part for all the shares will appear first and follows * with the comments for all those shares and that's why the data cannot be * encoded in one round without unnecessarily complicating the code. */ void smb_kshare_enum(smb_server_t *sv, smb_enumshare_info_t *esi) { smb_avl_t *share_avl; smb_avl_cursor_t cursor; smb_kshare_t *shr; int remained; uint16_t infolen = 0; uint16_t cmntlen = 0; uint16_t sharelen; uint16_t clen; uint32_t cmnt_offs; smb_msgbuf_t info_mb; smb_msgbuf_t cmnt_mb; boolean_t autohome_added = B_FALSE; if (!smb_export_isready(sv)) { esi->es_ntotal = esi->es_nsent = 0; esi->es_datasize = 0; return; } esi->es_ntotal = esi->es_nsent = 0; remained = esi->es_bufsize; share_avl = &sv->sv_export.e_share_avl; /* Do the necessary calculations in the first round */ smb_avl_iterinit(share_avl, &cursor); while ((shr = smb_avl_iterate(share_avl, &cursor)) != NULL) { if (shr->shr_oemname == NULL) { smb_avl_release(share_avl, shr); continue; } if ((shr->shr_flags & SMB_SHRF_AUTOHOME) && !autohome_added) { if (esi->es_posix_uid == shr->shr_uid) { autohome_added = B_TRUE; } else { smb_avl_release(share_avl, shr); continue; } } esi->es_ntotal++; if (remained <= 0) { smb_avl_release(share_avl, shr); continue; } clen = strlen(shr->shr_cmnt) + 1; sharelen = SHARE_INFO_1_SIZE + clen; if (sharelen <= remained) { infolen += SHARE_INFO_1_SIZE; cmntlen += clen; } remained -= sharelen; smb_avl_release(share_avl, shr); } esi->es_datasize = infolen + cmntlen; smb_msgbuf_init(&info_mb, (uint8_t *)esi->es_buf, infolen, 0); smb_msgbuf_init(&cmnt_mb, (uint8_t *)esi->es_buf + infolen, cmntlen, 0); cmnt_offs = infolen; /* Encode the data in the second round */ smb_avl_iterinit(share_avl, &cursor); autohome_added = B_FALSE; while ((shr = smb_avl_iterate(share_avl, &cursor)) != NULL) { if (shr->shr_oemname == NULL) { smb_avl_release(share_avl, shr); continue; } if ((shr->shr_flags & SMB_SHRF_AUTOHOME) && !autohome_added) { if (esi->es_posix_uid == shr->shr_uid) { autohome_added = B_TRUE; } else { smb_avl_release(share_avl, shr); continue; } } if (smb_msgbuf_encode(&info_mb, "13c.wl", shr->shr_oemname, shr->shr_type, cmnt_offs) < 0) { smb_avl_release(share_avl, shr); break; } if (smb_msgbuf_encode(&cmnt_mb, "s", shr->shr_cmnt) < 0) { smb_avl_release(share_avl, shr); break; } cmnt_offs += strlen(shr->shr_cmnt) + 1; esi->es_nsent++; smb_avl_release(share_avl, shr); } smb_msgbuf_term(&info_mb); smb_msgbuf_term(&cmnt_mb); } /* * Looks up the given share and returns a pointer * to its definition if it's found. A hold on the * object is taken before the pointer is returned * in which case the caller MUST always call * smb_kshare_release(). */ smb_kshare_t * smb_kshare_lookup(smb_server_t *sv, const char *shrname) { smb_kshare_t key; smb_kshare_t *shr; ASSERT(shrname); if (!smb_export_isready(sv)) return (NULL); key.shr_name = (char *)shrname; shr = smb_avl_lookup(&sv->sv_export.e_share_avl, &key); return (shr); } /* * Releases the hold taken on the specified share object */ void smb_kshare_release(smb_server_t *sv, smb_kshare_t *shr) { ASSERT(shr); ASSERT(shr->shr_magic == SMB_SHARE_MAGIC); smb_avl_release(&sv->sv_export.e_share_avl, shr); } /* * Add the given share in the specified server. * If the share is a disk share, lookup the share path * and hold the smb_node_t for the share root. * * If the share is an Autohome share and it is * already in the AVL only a reference count for * that share is incremented. */ static int smb_kshare_export(smb_server_t *sv, smb_kshare_t *shr) { smb_avl_t *share_avl; smb_kshare_t *auto_shr; smb_node_t *snode = NULL; int rc = 0; share_avl = &sv->sv_export.e_share_avl; if (!STYPE_ISDSK(shr->shr_type)) { if ((rc = smb_avl_add(share_avl, shr)) != 0) { cmn_err(CE_WARN, "export[%s]: failed caching (%d)", shr->shr_name, rc); } return (rc); } if ((auto_shr = smb_avl_lookup(share_avl, shr)) != NULL) { rc = EEXIST; if ((auto_shr->shr_flags & SMB_SHRF_AUTOHOME) != 0) { mutex_enter(&auto_shr->shr_mutex); auto_shr->shr_autocnt++; mutex_exit(&auto_shr->shr_mutex); rc = 0; } smb_avl_release(share_avl, auto_shr); return (rc); } /* * Get the root smb_node_t for this share, held. * This hold is normally released during AVL destroy, * via the element destructor: smb_kshare_destroy */ rc = smb_server_share_lookup(sv, shr->shr_path, &snode); if (rc != 0) { cmn_err(CE_WARN, "export[%s(%s)]: lookup failed (%d)", shr->shr_name, shr->shr_path, rc); return (rc); } shr->shr_root_node = snode; if ((rc = smb_avl_add(share_avl, shr)) != 0) { cmn_err(CE_WARN, "export[%s]: failed caching (%d)", shr->shr_name, rc); shr->shr_root_node = NULL; smb_node_release(snode); return (rc); } /* * For CA shares, find or create the CA handle dir, * and (if restarted) import persistent handles. */ if ((shr->shr_flags & SMB_SHRF_CA) != 0) { rc = smb2_dh_new_ca_share(sv, shr); if (rc != 0) { /* Just make it a non-CA share. */ mutex_enter(&shr->shr_mutex); shr->shr_flags &= ~SMB_SHRF_CA; mutex_exit(&shr->shr_mutex); rc = 0; } } return (rc); } /* * Removes the share specified by 'shrname' from the AVL * tree of the given server if it's there. * * If the share is an Autohome share, the autohome count * is decremented and the share is only removed if the * count goes to zero. * * If the share is a disk share, the hold on the corresponding * file system is released before removing the share from * the AVL tree. */ static int smb_kshare_unexport(smb_server_t *sv, const char *shrname) { smb_avl_t *share_avl; smb_kshare_t key; smb_kshare_t *shr; boolean_t auto_unexport; share_avl = &sv->sv_export.e_share_avl; key.shr_name = (char *)shrname; if ((shr = smb_avl_lookup(share_avl, &key)) == NULL) return (ENOENT); if ((shr->shr_flags & SMB_SHRF_AUTOHOME) != 0) { mutex_enter(&shr->shr_mutex); shr->shr_autocnt--; auto_unexport = (shr->shr_autocnt == 0); mutex_exit(&shr->shr_mutex); if (!auto_unexport) { smb_avl_release(share_avl, shr); return (0); } } smb_avl_remove(share_avl, shr); mutex_enter(&shr->shr_mutex); shr->shr_flags |= SMB_SHRF_REMOVED; mutex_exit(&shr->shr_mutex); smb_avl_release(share_avl, shr); return (0); } /* * Exports IPC$ or Admin shares */ static int smb_kshare_export_trans(smb_server_t *sv, char *name, char *path, char *cmnt) { smb_kshare_t *shr; ASSERT(name); ASSERT(path); shr = kmem_cache_alloc(smb_kshare_cache_share, KM_SLEEP); bzero(shr, sizeof (smb_kshare_t)); shr->shr_magic = SMB_SHARE_MAGIC; shr->shr_refcnt = 1; shr->shr_flags = SMB_SHRF_TRANS | smb_kshare_is_admin(name); if (strcasecmp(name, "IPC$") == 0) shr->shr_type = STYPE_IPC; else shr->shr_type = STYPE_DISKTREE; shr->shr_type |= smb_kshare_is_special(name); shr->shr_name = smb_mem_strdup(name); if (path) shr->shr_path = smb_mem_strdup(path); if (cmnt) shr->shr_cmnt = smb_mem_strdup(cmnt); shr->shr_oemname = smb_kshare_oemname(name); return (smb_kshare_export(sv, shr)); } /* * Decodes share information in an nvlist format into a smb_kshare_t * structure. * * This is a temporary function and will be replaced by functions * provided by libsharev2 code after it's available. */ static smb_kshare_t * smb_kshare_decode(nvlist_t *share) { smb_kshare_t tmp; smb_kshare_t *shr; nvlist_t *smb; char *csc_name = NULL, *strbuf = NULL; int rc; ASSERT(share); bzero(&tmp, sizeof (smb_kshare_t)); rc = nvlist_lookup_string(share, "name", &tmp.shr_name); rc |= nvlist_lookup_string(share, "path", &tmp.shr_path); (void) nvlist_lookup_string(share, "desc", &tmp.shr_cmnt); ASSERT(tmp.shr_name && tmp.shr_path); rc |= nvlist_lookup_nvlist(share, "smb", &smb); if (rc != 0) { cmn_err(CE_WARN, "kshare: failed looking up SMB properties" " (%d)", rc); return (NULL); } rc = nvlist_lookup_uint32(smb, "type", &tmp.shr_type); if (rc != 0) { cmn_err(CE_WARN, "kshare[%s]: failed getting the share type" " (%d)", tmp.shr_name, rc); return (NULL); } (void) nvlist_lookup_string(smb, SHOPT_AD_CONTAINER, &tmp.shr_container); (void) nvlist_lookup_string(smb, SHOPT_NONE, &tmp.shr_access_none); (void) nvlist_lookup_string(smb, SHOPT_RO, &tmp.shr_access_ro); (void) nvlist_lookup_string(smb, SHOPT_RW, &tmp.shr_access_rw); tmp.shr_flags |= smb_kshare_decode_bool(smb, SHOPT_ABE, SMB_SHRF_ABE); tmp.shr_flags |= smb_kshare_decode_bool(smb, SHOPT_CATIA, SMB_SHRF_CATIA); tmp.shr_flags |= smb_kshare_decode_bool(smb, SHOPT_GUEST, SMB_SHRF_GUEST_OK); tmp.shr_flags |= smb_kshare_decode_bool(smb, SHOPT_DFSROOT, SMB_SHRF_DFSROOT); tmp.shr_flags |= smb_kshare_decode_bool(smb, SHOPT_QUOTAS, SMB_SHRF_QUOTAS); tmp.shr_flags |= smb_kshare_decode_bool(smb, SHOPT_CA, SMB_SHRF_CA); tmp.shr_flags |= smb_kshare_decode_bool(smb, SHOPT_FSO, SMB_SHRF_FSO); tmp.shr_flags |= smb_kshare_decode_bool(smb, SHOPT_AUTOHOME, SMB_SHRF_AUTOHOME); if ((tmp.shr_flags & SMB_SHRF_AUTOHOME) == SMB_SHRF_AUTOHOME) { rc = nvlist_lookup_uint32(smb, "uid", &tmp.shr_uid); rc |= nvlist_lookup_uint32(smb, "gid", &tmp.shr_gid); if (rc != 0) { cmn_err(CE_WARN, "kshare: failed looking up uid/gid" " (%d)", rc); return (NULL); } } (void) nvlist_lookup_string(smb, SHOPT_ENCRYPT, &strbuf); smb_cfg_set_require(strbuf, &tmp.shr_encrypt); (void) nvlist_lookup_string(smb, SHOPT_CSC, &csc_name); smb_kshare_csc_flags(&tmp, csc_name); shr = kmem_cache_alloc(smb_kshare_cache_share, KM_SLEEP); bzero(shr, sizeof (smb_kshare_t)); shr->shr_magic = SMB_SHARE_MAGIC; shr->shr_refcnt = 1; shr->shr_name = smb_mem_strdup(tmp.shr_name); shr->shr_path = smb_mem_strdup(tmp.shr_path); if (tmp.shr_cmnt) shr->shr_cmnt = smb_mem_strdup(tmp.shr_cmnt); if (tmp.shr_container) shr->shr_container = smb_mem_strdup(tmp.shr_container); if (tmp.shr_access_none) shr->shr_access_none = smb_mem_strdup(tmp.shr_access_none); if (tmp.shr_access_ro) shr->shr_access_ro = smb_mem_strdup(tmp.shr_access_ro); if (tmp.shr_access_rw) shr->shr_access_rw = smb_mem_strdup(tmp.shr_access_rw); shr->shr_oemname = smb_kshare_oemname(shr->shr_name); shr->shr_flags = tmp.shr_flags | smb_kshare_is_admin(shr->shr_name); shr->shr_type = tmp.shr_type | smb_kshare_is_special(shr->shr_name); shr->shr_encrypt = tmp.shr_encrypt; shr->shr_uid = tmp.shr_uid; shr->shr_gid = tmp.shr_gid; if ((shr->shr_flags & SMB_SHRF_AUTOHOME) == SMB_SHRF_AUTOHOME) shr->shr_autocnt = 1; return (shr); } #if 0 static void smb_kshare_log(smb_kshare_t *shr) { cmn_err(CE_NOTE, "Share info:"); cmn_err(CE_NOTE, "\tname: %s", (shr->shr_name) ? shr->shr_name : ""); cmn_err(CE_NOTE, "\tpath: %s", (shr->shr_path) ? shr->shr_path : ""); cmn_err(CE_NOTE, "\tcmnt: (%s)", (shr->shr_cmnt) ? shr->shr_cmnt : "NULL"); cmn_err(CE_NOTE, "\toemname: (%s)", (shr->shr_oemname) ? shr->shr_oemname : "NULL"); cmn_err(CE_NOTE, "\tflags: %X", shr->shr_flags); cmn_err(CE_NOTE, "\ttype: %d", shr->shr_type); } #endif /* * Compare function used by shares AVL */ static int smb_kshare_cmp(const void *p1, const void *p2) { smb_kshare_t *shr1 = (smb_kshare_t *)p1; smb_kshare_t *shr2 = (smb_kshare_t *)p2; int rc; ASSERT(shr1); ASSERT(shr1->shr_name); ASSERT(shr2); ASSERT(shr2->shr_name); rc = smb_strcasecmp(shr1->shr_name, shr2->shr_name, 0); if (rc < 0) return (-1); if (rc > 0) return (1); return (0); } /* * This function is called by smb_avl routines whenever * there is a need to take a hold on a share structure * inside AVL */ static void smb_kshare_hold(const void *p) { smb_kshare_t *shr = (smb_kshare_t *)p; ASSERT(shr); ASSERT(shr->shr_magic == SMB_SHARE_MAGIC); mutex_enter(&shr->shr_mutex); shr->shr_refcnt++; mutex_exit(&shr->shr_mutex); } /* * This function must be called by smb_avl routines whenever * smb_kshare_hold is called and the hold needs to be released. */ static boolean_t smb_kshare_rele(const void *p) { smb_kshare_t *shr = (smb_kshare_t *)p; boolean_t destroy; ASSERT(shr); ASSERT(shr->shr_magic == SMB_SHARE_MAGIC); mutex_enter(&shr->shr_mutex); ASSERT(shr->shr_refcnt > 0); shr->shr_refcnt--; destroy = (shr->shr_refcnt == 0); mutex_exit(&shr->shr_mutex); return (destroy); } /* * Frees all the memory allocated for the given * share structure. It also removes the structure * from the share cache. */ static void smb_kshare_destroy(void *p) { smb_kshare_t *shr = (smb_kshare_t *)p; ASSERT(shr); ASSERT(shr->shr_magic == SMB_SHARE_MAGIC); if (shr->shr_ca_dir != NULL) smb_node_release(shr->shr_ca_dir); if (shr->shr_root_node) smb_node_release(shr->shr_root_node); smb_mem_free(shr->shr_name); smb_mem_free(shr->shr_path); smb_mem_free(shr->shr_cmnt); smb_mem_free(shr->shr_container); smb_mem_free(shr->shr_oemname); smb_mem_free(shr->shr_access_none); smb_mem_free(shr->shr_access_ro); smb_mem_free(shr->shr_access_rw); kmem_cache_free(smb_kshare_cache_share, shr); } /* * Generate an OEM name for the given share name. If the name is * shorter than 13 bytes the oemname will be returned; otherwise NULL * is returned. */ static char * smb_kshare_oemname(const char *shrname) { smb_wchar_t *unibuf; char *oem_name; int length; length = strlen(shrname) + 1; oem_name = smb_mem_alloc(length); unibuf = smb_mem_alloc(length * sizeof (smb_wchar_t)); (void) smb_mbstowcs(unibuf, shrname, length); if (ucstooem(oem_name, unibuf, length, OEM_CPG_850) == 0) (void) strcpy(oem_name, shrname); smb_mem_free(unibuf); if (strlen(oem_name) + 1 > SMB_SHARE_OEMNAME_MAX) { smb_mem_free(oem_name); return (NULL); } return (oem_name); } /* * Special share reserved for interprocess communication (IPC$) or * remote administration of the server (ADMIN$). Can also refer to * administrative shares such as C$, D$, E$, and so forth. */ static int smb_kshare_is_special(const char *sharename) { int len; if (sharename == NULL) return (0); if ((len = strlen(sharename)) == 0) return (0); if (sharename[len - 1] == '$') return (STYPE_SPECIAL); return (0); } /* * Check whether or not this is a default admin share: C$, D$ etc. */ static int smb_kshare_is_admin(const char *sharename) { if (sharename == NULL) return (0); if (strlen(sharename) == 2 && smb_isalpha(sharename[0]) && sharename[1] == '$') { return (SMB_SHRF_ADMIN); } return (0); } /* * Decodes the given boolean share option. * If the option is present in the nvlist and it's value is true * returns the corresponding flag value, otherwise returns 0. */ static uint32_t smb_kshare_decode_bool(nvlist_t *nvl, const char *propname, uint32_t flag) { char *boolp; if (nvlist_lookup_string(nvl, propname, &boolp) == 0) if (strcasecmp(boolp, "true") == 0) return (flag); return (0); } /* * Map a client-side caching (CSC) option to the appropriate share * flag. Only one option is allowed; an error will be logged if * multiple options have been specified. We don't need to do anything * about multiple values here because the SRVSVC will not recognize * a value containing multiple flags and will return the default value. * * If the option value is not recognized, it will be ignored: invalid * values will typically be caught and rejected by sharemgr. */ static void smb_kshare_csc_flags(smb_kshare_t *shr, const char *value) { int i; static struct { char *value; uint32_t flag; } cscopt[] = { { "disabled", SMB_SHRF_CSC_DISABLED }, { "manual", SMB_SHRF_CSC_MANUAL }, { "auto", SMB_SHRF_CSC_AUTO }, { "vdo", SMB_SHRF_CSC_VDO } }; if (value == NULL) return; for (i = 0; i < (sizeof (cscopt) / sizeof (cscopt[0])); ++i) { if (strcasecmp(value, cscopt[i].value) == 0) { shr->shr_flags |= cscopt[i].flag; break; } } switch (shr->shr_flags & SMB_SHRF_CSC_MASK) { case 0: case SMB_SHRF_CSC_DISABLED: case SMB_SHRF_CSC_MANUAL: case SMB_SHRF_CSC_AUTO: case SMB_SHRF_CSC_VDO: break; default: cmn_err(CE_NOTE, "csc option conflict: 0x%08x", shr->shr_flags & SMB_SHRF_CSC_MASK); break; } } /* * This function processes the unexport event list and disconnects shares * asynchronously. The function executes as a zone-specific thread. * * The server arg passed in is safe to use without a reference count, because * the server cannot be deleted until smb_thread_stop()/destroy() return, * which is also when the thread exits. */ /*ARGSUSED*/ static void smb_kshare_unexport_thread(smb_thread_t *thread, void *arg) { smb_server_t *sv = arg; smb_unshare_t *ux; while (smb_thread_continue(thread)) { while ((ux = list_head(&sv->sv_export.e_unexport_list.sl_list)) != NULL) { smb_slist_remove(&sv->sv_export.e_unexport_list, ux); (void) smb_server_unshare(ux->us_sharename); kmem_cache_free(smb_kshare_cache_unexport, ux); } } } static boolean_t smb_export_isready(smb_server_t *sv) { boolean_t ready; mutex_enter(&sv->sv_export.e_mutex); ready = sv->sv_export.e_ready; mutex_exit(&sv->sv_export.e_mutex); return (ready); } #ifdef _KERNEL /* * Return 0 upon success. Otherwise > 0 */ static int smb_kshare_chk_dsrv_status(int opcode, smb_dr_ctx_t *dec_ctx) { int status = smb_dr_get_int32(dec_ctx); int err; switch (status) { case SMB_SHARE_DSUCCESS: return (0); case SMB_SHARE_DERROR: err = smb_dr_get_uint32(dec_ctx); cmn_err(CE_WARN, "%d: Encountered door server error %d", opcode, err); (void) smb_dr_decode_finish(dec_ctx); return (err); } ASSERT(0); return (EINVAL); } #endif /* _KERNEL */