/* * 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. */ /* * This file defines the domain environment values and the domain * database interface. The database is a single linked list of * structures containing domain type, name and SID information. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SMB_DOMAINS_FILE "domains" #define SMB_DCACHE_UPDATE_WAIT 45 /* seconds */ /* * Domain cache states */ #define SMB_DCACHE_STATE_NONE 0 #define SMB_DCACHE_STATE_READY 1 #define SMB_DCACHE_STATE_UPDATING 2 #define SMB_DCACHE_STATE_DESTROYING 3 /* * Cache lock modes */ #define SMB_DCACHE_RDLOCK 0 #define SMB_DCACHE_WRLOCK 1 typedef struct smb_domain_cache { list_t dc_cache; rwlock_t dc_cache_lck; mutex_t dc_mtx; cond_t dc_cv; uint32_t dc_state; uint32_t dc_nops; char dc_server[MAXHOSTNAMELEN]; } smb_domain_cache_t; static smb_domain_cache_t smb_dcache; static uint32_t nt_domain_add(nt_domain_type_t, nt_domain_t *); static uint32_t nt_domain_add_local(void); static uint32_t nt_domain_add_primary(uint32_t); static void nt_domain_unlink(void); static void smb_dcache_create(void); static void smb_dcache_destroy(void); static uint32_t smb_dcache_lock(int); static void smb_dcache_unlock(void); static void smb_dcache_remove(nt_domain_t *); static uint32_t smb_dcache_add(nt_domain_t *); static void smb_dcache_getdc(char *, size_t); static void smb_dcache_setdc(char *); static boolean_t smb_dcache_wait(void); static void smb_dcache_updating(void); static void smb_dcache_ready(void); /* * domain cache one time initialization. This function should * only be called during service startup. * * Returns 0 on success and an error code on failure. */ int nt_domain_init(uint32_t secmode) { nt_domain_t di; int rc; smb_dcache_create(); if ((rc = nt_domain_add_local()) != 0) return (rc); nt_domain_set_basic_info(NT_BUILTIN_DOMAIN_SIDSTR, "BUILTIN", "", &di); (void) nt_domain_add(NT_DOMAIN_BUILTIN, &di); return (nt_domain_add_primary(secmode)); } /* * Destroys the cache upon service termination */ void nt_domain_fini(void) { smb_dcache_destroy(); nt_domain_unlink(); } /* * Add a domain structure to domain cache. There is no checking * for duplicates. */ static uint32_t nt_domain_add(nt_domain_type_t type, nt_domain_t *di) { uint32_t res; if ((di == NULL) || (di->di_sid == NULL)) return (SMB_DOMAIN_INVALID_ARG); if ((res = smb_dcache_lock(SMB_DCACHE_WRLOCK)) == SMB_DOMAIN_SUCCESS) { di->di_type = type; res = smb_dcache_add(di); smb_dcache_unlock(); } return (res); } /* * Lookup a domain by its name. The passed name is the NETBIOS or fully * qualified DNS name or non-qualified DNS name. * * If the requested domain is found and given 'di' pointer is not NULL * it'll be filled with the domain information and B_TRUE is returned. * If the caller only needs to check a domain existence it can pass * NULL for 'di' and just check the return value. * * If the domain is not in the cache B_FALSE is returned. */ boolean_t nt_domain_lookup_name(char *name, nt_domain_t *di) { boolean_t found = B_FALSE; nt_domain_t *dcnode; char *p; bzero(di, sizeof (nt_domain_t)); if (name == NULL || *name == '\0') return (B_FALSE); if (smb_dcache_lock(SMB_DCACHE_RDLOCK) != SMB_DOMAIN_SUCCESS) return (B_FALSE); dcnode = list_head(&smb_dcache.dc_cache); while (dcnode) { found = (utf8_strcasecmp(dcnode->di_nbname, name) == 0) || (utf8_strcasecmp(dcnode->di_fqname, name) == 0); if (found) { if (di) *di = *dcnode; break; } if ((p = strchr(dcnode->di_fqname, '.')) != NULL) { *p = '\0'; found = (utf8_strcasecmp(dcnode->di_fqname, name) == 0); *p = '.'; if (found) { if (di) *di = *dcnode; break; } } dcnode = list_next(&smb_dcache.dc_cache, dcnode); } smb_dcache_unlock(); return (found); } /* * Lookup a domain by its SID. * * If the requested domain is found and given 'di' pointer is not NULL * it'll be filled with the domain information and B_TRUE is returned. * If the caller only needs to check a domain existence it can pass * NULL for 'di' and just check the return value. * * If the domain is not in the cache B_FALSE is returned. */ boolean_t nt_domain_lookup_sid(smb_sid_t *sid, nt_domain_t *di) { boolean_t found = B_FALSE; nt_domain_t *dcnode; char sidstr[SMB_SID_STRSZ]; bzero(di, sizeof (nt_domain_t)); if (sid == NULL) return (B_FALSE); smb_sid_tostr(sid, sidstr); if (smb_dcache_lock(SMB_DCACHE_RDLOCK) != SMB_DOMAIN_SUCCESS) return (B_FALSE); dcnode = list_head(&smb_dcache.dc_cache); while (dcnode) { found = (strcmp(dcnode->di_sid, sidstr) == 0); if (found) { if (di) *di = *dcnode; break; } dcnode = list_next(&smb_dcache.dc_cache, dcnode); } smb_dcache_unlock(); return (found); } /* * Lookup a domain by its type. * * If the requested domain is found and given 'di' pointer is not NULL * it'll be filled with the domain information and B_TRUE is returned. * If the caller only needs to check a domain existence it can pass * NULL for 'di' and just check the return value. * * If the domain is not in the cache B_FALSE is returned. */ boolean_t nt_domain_lookup_type(nt_domain_type_t type, nt_domain_t *di) { boolean_t found = B_FALSE; nt_domain_t *dcnode; bzero(di, sizeof (nt_domain_t)); if (smb_dcache_lock(SMB_DCACHE_RDLOCK) != SMB_DOMAIN_SUCCESS) return (B_FALSE); dcnode = list_head(&smb_dcache.dc_cache); while (dcnode) { if (dcnode->di_type == type) { found = B_TRUE; if (di) *di = *dcnode; break; } dcnode = list_next(&smb_dcache.dc_cache, dcnode); } smb_dcache_unlock(); return (found); } /* * Returns primary domain information plus the name of * the selected domain controller. */ boolean_t nt_domain_get_primary(smb_domain_t *domain) { boolean_t success; success = nt_domain_lookup_type(NT_DOMAIN_PRIMARY, &domain->d_info); if (success) smb_dcache_getdc(domain->d_dc, sizeof (domain->d_dc)); return (success); } /* * Transfer the cache to updating state. * In this state any request for reading the cache would * be blocked until the update is finished. */ void nt_domain_start_update(void) { smb_dcache_updating(); } /* * Transfer the cache from updating to ready state. */ void nt_domain_end_update(void) { smb_dcache_ready(); } /* * Updates the cache with given information for the primary * domain, possible trusted domains and the selected domain * controller. * * Before adding the new entries existing entries of type * primary and trusted will be removed from cache. */ void nt_domain_update(smb_domain_t *domain) { nt_domain_t *dcnode; int i; if (smb_dcache_lock(SMB_DCACHE_WRLOCK) != SMB_DOMAIN_SUCCESS) return; dcnode = list_head(&smb_dcache.dc_cache); while (dcnode) { if ((dcnode->di_type == NT_DOMAIN_PRIMARY) || (dcnode->di_type == NT_DOMAIN_TRUSTED)) { smb_dcache_remove(dcnode); dcnode = list_head(&smb_dcache.dc_cache); } else { dcnode = list_next(&smb_dcache.dc_cache, dcnode); } } if (smb_dcache_add(&domain->d_info) == SMB_DOMAIN_SUCCESS) { for (i = 0; i < domain->d_trusted.td_num; i++) (void) smb_dcache_add(&domain->d_trusted.td_domains[i]); smb_dcache_setdc(domain->d_dc); } smb_dcache_unlock(); } /* * Write the list of domains to /var/run/smb/domains. */ void nt_domain_save(void) { char fname[MAXPATHLEN]; char tag; nt_domain_t *domain; FILE *fp; struct passwd *pwd; struct group *grp; uid_t uid; gid_t gid; (void) snprintf(fname, MAXPATHLEN, "%s/%s", SMB_VARRUN_DIR, SMB_DOMAINS_FILE); if ((fp = fopen(fname, "w")) == NULL) return; pwd = getpwnam("root"); grp = getgrnam("sys"); uid = (pwd == NULL) ? 0 : pwd->pw_uid; gid = (grp == NULL) ? 3 : grp->gr_gid; (void) lockf(fileno(fp), F_LOCK, 0); (void) fchmod(fileno(fp), 0600); (void) fchown(fileno(fp), uid, gid); if (smb_dcache_lock(SMB_DCACHE_RDLOCK) != SMB_DOMAIN_SUCCESS) return; domain = list_head(&smb_dcache.dc_cache); while (domain) { switch (domain->di_type) { case NT_DOMAIN_PRIMARY: tag = '*'; break; case NT_DOMAIN_TRUSTED: case NT_DOMAIN_UNTRUSTED: tag = '-'; break; case NT_DOMAIN_LOCAL: tag = '.'; break; default: domain = list_next(&smb_dcache.dc_cache, domain); continue; } (void) fprintf(fp, "[%c] [%s] [%s]\n", tag, domain->di_nbname, domain->di_sid); domain = list_next(&smb_dcache.dc_cache, domain); } smb_dcache_unlock(); (void) lockf(fileno(fp), F_ULOCK, 0); (void) fclose(fp); } /* * List the domains in /var/run/smb/domains. */ void nt_domain_show(void) { char buf[MAXPATHLEN]; char *p; FILE *fp; (void) snprintf(buf, MAXPATHLEN, "%s/%s", SMB_VARRUN_DIR, SMB_DOMAINS_FILE); if ((fp = fopen(buf, "r")) != NULL) { (void) lockf(fileno(fp), F_LOCK, 0); while (fgets(buf, MAXPATHLEN, fp) != NULL) { if ((p = strchr(buf, '\n')) != NULL) *p = '\0'; (void) printf("%s\n", buf); } (void) lockf(fileno(fp), F_ULOCK, 0); (void) fclose(fp); } } void nt_domain_set_basic_info(char *sid, char *nb_domain, char *fq_domain, nt_domain_t *di) { if (sid == NULL || nb_domain == NULL || fq_domain == NULL || di == NULL) return; (void) strlcpy(di->di_sid, sid, SMB_SID_STRSZ); (void) strlcpy(di->di_nbname, nb_domain, NETBIOS_NAME_SZ); (void) utf8_strupr(di->di_nbname); (void) strlcpy(di->di_fqname, fq_domain, MAXHOSTNAMELEN); di->di_binsid = NULL; } void nt_domain_set_dns_info(char *sid, char *nb_domain, char *fq_domain, char *forest, char *guid, nt_domain_t *di) { if (di == NULL || forest == NULL || guid == NULL) return; nt_domain_set_basic_info(sid, nb_domain, fq_domain, di); (void) strlcpy(di->di_u.di_dns.ddi_forest, forest, MAXHOSTNAMELEN); (void) strlcpy(di->di_u.di_dns.ddi_guid, guid, UUID_PRINTABLE_STRING_LENGTH); } void nt_domain_set_trust_info(char *sid, char *nb_domain, char *fq_domain, uint32_t trust_dir, uint32_t trust_type, uint32_t trust_attrs, nt_domain_t *di) { smb_domain_trust_t *ti; if (di == NULL) return; di->di_type = NT_DOMAIN_TRUSTED; ti = &di->di_u.di_trust; nt_domain_set_basic_info(sid, nb_domain, fq_domain, di); ti->dti_trust_direction = trust_dir; ti->dti_trust_type = trust_type; ti->dti_trust_attrs = trust_attrs; } /* * Remove the /var/run/smb/domains file. */ static void nt_domain_unlink(void) { char fname[MAXPATHLEN]; (void) snprintf(fname, MAXPATHLEN, "%s/%s", SMB_VARRUN_DIR, SMB_DOMAINS_FILE); (void) unlink(fname); } /* * Add an entry for the local domain to the domain cache */ static uint32_t nt_domain_add_local(void) { char *lsidstr; char hostname[NETBIOS_NAME_SZ]; char fq_name[MAXHOSTNAMELEN]; nt_domain_t di; if ((lsidstr = smb_config_get_localsid()) == NULL) return (SMB_DOMAIN_NOMACHINE_SID); if (smb_getnetbiosname(hostname, NETBIOS_NAME_SZ) != 0) { free(lsidstr); return (SMB_DOMAIN_NOMACHINE_SID); } *fq_name = '\0'; (void) smb_getfqhostname(fq_name, MAXHOSTNAMELEN); nt_domain_set_basic_info(lsidstr, hostname, fq_name, &di); (void) nt_domain_add(NT_DOMAIN_LOCAL, &di); free(lsidstr); return (SMB_DOMAIN_SUCCESS); } /* * Add an entry for the primary domain to the domain cache */ static uint32_t nt_domain_add_primary(uint32_t secmode) { char sidstr[SMB_SID_STRSZ]; char fq_name[MAXHOSTNAMELEN]; char nb_name[NETBIOS_NAME_SZ]; nt_domain_t di; int rc; if (secmode != SMB_SECMODE_DOMAIN) return (SMB_DOMAIN_SUCCESS); rc = smb_config_getstr(SMB_CI_DOMAIN_SID, sidstr, sizeof (sidstr)); if (rc != SMBD_SMF_OK) return (SMB_DOMAIN_NODOMAIN_SID); rc = smb_config_getstr(SMB_CI_DOMAIN_NAME, nb_name, NETBIOS_NAME_SZ); if ((rc != SMBD_SMF_OK) || (*nb_name == '\0')) return (SMB_DOMAIN_NODOMAIN_NAME); (void) smb_getfqdomainname(fq_name, MAXHOSTNAMELEN); nt_domain_set_basic_info(sidstr, nb_name, fq_name, &di); (void) nt_domain_add(NT_DOMAIN_PRIMARY, &di); return (SMB_DOMAIN_SUCCESS); } /* * Initialize the domain cache. * This function does not populate the cache. */ static void smb_dcache_create(void) { (void) mutex_lock(&smb_dcache.dc_mtx); if (smb_dcache.dc_state != SMB_DCACHE_STATE_NONE) { (void) mutex_unlock(&smb_dcache.dc_mtx); return; } list_create(&smb_dcache.dc_cache, sizeof (nt_domain_t), offsetof(nt_domain_t, di_lnd)); smb_dcache.dc_nops = 0; *smb_dcache.dc_server = '\0'; smb_dcache.dc_state = SMB_DCACHE_STATE_READY; (void) mutex_unlock(&smb_dcache.dc_mtx); } /* * Removes and frees all the cache entries */ static void smb_dcache_flush(void) { nt_domain_t *di; (void) rw_wrlock(&smb_dcache.dc_cache_lck); while ((di = list_head(&smb_dcache.dc_cache)) != NULL) smb_dcache_remove(di); (void) rw_unlock(&smb_dcache.dc_cache_lck); } /* * Destroys the cache. */ static void smb_dcache_destroy(void) { (void) mutex_lock(&smb_dcache.dc_mtx); if ((smb_dcache.dc_state == SMB_DCACHE_STATE_READY) || (smb_dcache.dc_state == SMB_DCACHE_STATE_UPDATING)) { smb_dcache.dc_state = SMB_DCACHE_STATE_DESTROYING; while (smb_dcache.dc_nops > 0) (void) cond_wait(&smb_dcache.dc_cv, &smb_dcache.dc_mtx); smb_dcache_flush(); list_destroy(&smb_dcache.dc_cache); smb_dcache.dc_state = SMB_DCACHE_STATE_NONE; } (void) mutex_unlock(&smb_dcache.dc_mtx); } /* * Lock the cache with the specified mode. * If the cache is in updating state and a read lock is * requested, the lock won't be granted until either the * update is finished or SMB_DCACHE_UPDATE_WAIT has passed. * * Whenever a lock is granted, the number of inflight cache * operations is incremented. */ static uint32_t smb_dcache_lock(int mode) { (void) mutex_lock(&smb_dcache.dc_mtx); switch (smb_dcache.dc_state) { case SMB_DCACHE_STATE_NONE: case SMB_DCACHE_STATE_DESTROYING: (void) mutex_unlock(&smb_dcache.dc_mtx); return (SMB_DOMAIN_INTERNAL_ERR); case SMB_DCACHE_STATE_UPDATING: if (mode == SMB_DCACHE_RDLOCK) { /* * Read operations should wait until the update * is completed. */ if (!smb_dcache_wait()) { (void) mutex_unlock(&smb_dcache.dc_mtx); return (SMB_DOMAIN_INTERNAL_ERR); } } default: smb_dcache.dc_nops++; break; } (void) mutex_unlock(&smb_dcache.dc_mtx); /* * Lock has to be taken outside the mutex otherwise * there could be a deadlock */ if (mode == SMB_DCACHE_RDLOCK) (void) rw_rdlock(&smb_dcache.dc_cache_lck); else (void) rw_wrlock(&smb_dcache.dc_cache_lck); return (SMB_DOMAIN_SUCCESS); } /* * Decrement the number of inflight operations and then unlock. */ static void smb_dcache_unlock(void) { (void) mutex_lock(&smb_dcache.dc_mtx); assert(smb_dcache.dc_nops > 0); smb_dcache.dc_nops--; (void) cond_broadcast(&smb_dcache.dc_cv); (void) mutex_unlock(&smb_dcache.dc_mtx); (void) rw_unlock(&smb_dcache.dc_cache_lck); } static uint32_t smb_dcache_add(nt_domain_t *di) { nt_domain_t *dcnode; if ((dcnode = malloc(sizeof (nt_domain_t))) == NULL) return (SMB_DOMAIN_NO_MEMORY); *dcnode = *di; dcnode->di_binsid = smb_sid_fromstr(dcnode->di_sid); if (dcnode->di_binsid == NULL) { free(dcnode); return (SMB_DOMAIN_NO_MEMORY); } list_insert_tail(&smb_dcache.dc_cache, dcnode); return (SMB_DOMAIN_SUCCESS); } static void smb_dcache_remove(nt_domain_t *di) { list_remove(&smb_dcache.dc_cache, di); smb_sid_free(di->di_binsid); free(di); } static void smb_dcache_setdc(char *dc) { (void) mutex_lock(&smb_dcache.dc_mtx); (void) strlcpy(smb_dcache.dc_server, dc, sizeof (smb_dcache.dc_server)); (void) mutex_unlock(&smb_dcache.dc_mtx); } static void smb_dcache_getdc(char *buf, size_t buflen) { (void) mutex_lock(&smb_dcache.dc_mtx); (void) strlcpy(buf, smb_dcache.dc_server, buflen); (void) mutex_unlock(&smb_dcache.dc_mtx); } static boolean_t smb_dcache_wait(void) { timestruc_t to; int err; to.tv_sec = SMB_DCACHE_UPDATE_WAIT; to.tv_nsec = 0; while (smb_dcache.dc_state == SMB_DCACHE_STATE_UPDATING) { err = cond_reltimedwait(&smb_dcache.dc_cv, &smb_dcache.dc_mtx, &to); if (err == ETIME) break; } return (smb_dcache.dc_state == SMB_DCACHE_STATE_READY); } static void smb_dcache_updating(void) { (void) mutex_lock(&smb_dcache.dc_mtx); if (smb_dcache.dc_state == SMB_DCACHE_STATE_READY) smb_dcache.dc_state = SMB_DCACHE_STATE_UPDATING; else assert(0); (void) mutex_unlock(&smb_dcache.dc_mtx); } static void smb_dcache_ready(void) { (void) mutex_lock(&smb_dcache.dc_mtx); if (smb_dcache.dc_state == SMB_DCACHE_STATE_UPDATING) smb_dcache.dc_state = SMB_DCACHE_STATE_READY; else assert(0); (void) mutex_unlock(&smb_dcache.dc_mtx); }