1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
  24  * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
  25  */
  26 
  27 #include <syslog.h>
  28 #include <synch.h>
  29 #include <pthread.h>
  30 #include <unistd.h>
  31 #include <string.h>
  32 #include <strings.h>
  33 #include <sys/errno.h>
  34 #include <sys/types.h>
  35 #include <netinet/in.h>
  36 #include <arpa/nameser.h>
  37 #include <resolv.h>
  38 #include <netdb.h>
  39 #include <assert.h>
  40 
  41 #include <smbsrv/libsmb.h>
  42 #include <smbsrv/libsmbns.h>
  43 #include <smbsrv/libmlsvc.h>
  44 
  45 #include <smbsrv/smbinfo.h>
  46 #include <lsalib.h>
  47 #include <mlsvc.h>
  48 
  49 /*
  50  * DC Locator
  51  */
  52 #define SMB_DCLOCATOR_TIMEOUT   45      /* seconds */
  53 #define SMB_IS_FQDN(domain)     (strchr(domain, '.') != NULL)
  54 
  55 typedef struct smb_dclocator {
  56         smb_dcinfo_t    sdl_dci; /* .dc_name .dc_addr */
  57         char            sdl_domain[SMB_PI_MAX_DOMAIN];
  58         boolean_t       sdl_locate;
  59         boolean_t       sdl_bad_dc;
  60         boolean_t       sdl_cfg_chg;
  61         mutex_t         sdl_mtx;
  62         cond_t          sdl_cv;
  63         uint32_t        sdl_status;
  64 } smb_dclocator_t;
  65 
  66 static smb_dclocator_t smb_dclocator;
  67 static pthread_t smb_dclocator_thr;
  68 
  69 static void *smb_ddiscover_service(void *);
  70 static uint32_t smb_ddiscover_qinfo(char *, char *, smb_domainex_t *);
  71 static void smb_ddiscover_enum_trusted(char *, char *, smb_domainex_t *);
  72 static uint32_t smb_ddiscover_use_config(char *, smb_domainex_t *);
  73 static void smb_domainex_free(smb_domainex_t *);
  74 static void smb_set_krb5_realm(char *);
  75 
  76 /*
  77  * ===================================================================
  78  * API to initialize DC locator thread, trigger DC discovery, and
  79  * get the discovered DC and/or domain information.
  80  * ===================================================================
  81  */
  82 
  83 /*
  84  * Initialization of the DC locator thread.
  85  * Returns 0 on success, an error number if thread creation fails.
  86  */
  87 int
  88 smb_dclocator_init(void)
  89 {
  90         pthread_attr_t tattr;
  91         int rc;
  92 
  93         (void) pthread_attr_init(&tattr);
  94         (void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
  95         rc = pthread_create(&smb_dclocator_thr, &tattr,
  96             smb_ddiscover_service, &smb_dclocator);
  97         (void) pthread_attr_destroy(&tattr);
  98         return (rc);
  99 }
 100 
 101 /*
 102  * This is the entry point for discovering a domain controller for the
 103  * specified domain.  Called during join domain, and then periodically
 104  * by smbd_dc_update (the "DC monitor" thread).
 105  *
 106  * The actual work of discovering a DC is handled by DC locator thread.
 107  * All we do here is signal the request and wait for a DC or a timeout.
 108  *
 109  * Input parameters:
 110  *  domain - domain to be discovered (can either be NetBIOS or DNS domain)
 111  *
 112  * Output parameter:
 113  *  dp - on success, dp will be filled with the discovered DC and domain
 114  *       information.
 115  *
 116  * Returns B_TRUE if the DC/domain info is available.
 117  */
 118 boolean_t
 119 smb_locate_dc(char *domain, smb_domainex_t *dp)
 120 {
 121         int rc;
 122         boolean_t rv;
 123         timestruc_t to;
 124         smb_domainex_t domain_info;
 125 
 126         if (domain == NULL || *domain == '\0') {
 127                 syslog(LOG_DEBUG, "smb_locate_dc NULL dom");
 128                 smb_set_krb5_realm(NULL);
 129                 return (B_FALSE);
 130         }
 131 
 132         (void) mutex_lock(&smb_dclocator.sdl_mtx);
 133 
 134         if (strcmp(smb_dclocator.sdl_domain, domain)) {
 135                 (void) strlcpy(smb_dclocator.sdl_domain, domain,
 136                     sizeof (smb_dclocator.sdl_domain));
 137                 smb_dclocator.sdl_cfg_chg = B_TRUE;
 138                 syslog(LOG_DEBUG, "smb_locate_dc new dom=%s", domain);
 139                 smb_set_krb5_realm(domain);
 140         }
 141 
 142         if (!smb_dclocator.sdl_locate) {
 143                 smb_dclocator.sdl_locate = B_TRUE;
 144                 (void) cond_broadcast(&smb_dclocator.sdl_cv);
 145         }
 146 
 147         while (smb_dclocator.sdl_locate) {
 148                 to.tv_sec = SMB_DCLOCATOR_TIMEOUT;
 149                 to.tv_nsec = 0;
 150                 rc = cond_reltimedwait(&smb_dclocator.sdl_cv,
 151                     &smb_dclocator.sdl_mtx, &to);
 152 
 153                 if (rc == ETIME) {
 154                         syslog(LOG_NOTICE, "smb_locate_dc timeout");
 155                         rv = B_FALSE;
 156                         goto out;
 157                 }
 158         }
 159         if (smb_dclocator.sdl_status != 0) {
 160                 syslog(LOG_NOTICE, "smb_locate_dc status 0x%x",
 161                     smb_dclocator.sdl_status);
 162                 rv = B_FALSE;
 163                 goto out;
 164         }
 165 
 166         if (dp == NULL)
 167                 dp = &domain_info;
 168         rv = smb_domain_getinfo(dp);
 169 
 170 out:
 171         (void) mutex_unlock(&smb_dclocator.sdl_mtx);
 172 
 173         return (rv);
 174 }
 175 
 176 /*
 177  * Tell the domain discovery service to run again now,
 178  * and assume changed configuration (i.e. a new DC).
 179  * Like the first part of smb_locate_dc().
 180  *
 181  * Note: This is called from the service refresh handler
 182  * and the door handler to tell the ddiscover thread to
 183  * request the new DC from idmap.  Therefore, we must not
 184  * trigger a new idmap discovery run from here, or that
 185  * would start a ping-pong match.
 186  */
 187 /* ARGSUSED */
 188 void
 189 smb_ddiscover_refresh()
 190 {
 191 
 192         (void) mutex_lock(&smb_dclocator.sdl_mtx);
 193 
 194         if (smb_dclocator.sdl_cfg_chg == B_FALSE) {
 195                 smb_dclocator.sdl_cfg_chg = B_TRUE;
 196                 syslog(LOG_DEBUG, "smb_ddiscover_refresh set cfg changed");
 197         }
 198         if (!smb_dclocator.sdl_locate) {
 199                 smb_dclocator.sdl_locate = B_TRUE;
 200                 (void) cond_broadcast(&smb_dclocator.sdl_cv);
 201         }
 202 
 203         (void) mutex_unlock(&smb_dclocator.sdl_mtx);
 204 }
 205 
 206 /*
 207  * Called by our client-side threads after they fail to connect to
 208  * the DC given to them by smb_locate_dc().  This is often called
 209  * after some delay, because the connection timeout delays these
 210  * threads for a while, so it's quite common that the DC locator
 211  * service has already started looking for a new DC.  These late
 212  * notifications should not continually restart the DC locator.
 213  */
 214 void
 215 smb_ddiscover_bad_dc(char *bad_dc)
 216 {
 217 
 218         assert(bad_dc[0] != '\0');
 219 
 220         (void) mutex_lock(&smb_dclocator.sdl_mtx);
 221 
 222         syslog(LOG_DEBUG, "smb_ddiscover_bad_dc, cur=%s, bad=%s",
 223             smb_dclocator.sdl_dci.dc_name, bad_dc);
 224 
 225         if (strcmp(smb_dclocator.sdl_dci.dc_name, bad_dc)) {
 226                 /*
 227                  * The "bad" DC is no longer the current one.
 228                  * Probably a late "bad DC" report.
 229                  */
 230                 goto out;
 231         }
 232         if (smb_dclocator.sdl_bad_dc) {
 233                 /* Someone already marked the current DC as "bad". */
 234                 syslog(LOG_DEBUG, "smb_ddiscover_bad_dc repeat");
 235                 goto out;
 236         }
 237 
 238         /*
 239          * Mark the current DC as "bad" and let the DC Locator
 240          * run again if it's not already.
 241          */
 242         syslog(LOG_INFO, "smb_ddiscover, bad DC: %s", bad_dc);
 243         smb_dclocator.sdl_bad_dc = B_TRUE;
 244 
 245         /* In-line smb_ddiscover_kick */
 246         if (!smb_dclocator.sdl_locate) {
 247                 smb_dclocator.sdl_locate = B_TRUE;
 248                 (void) cond_broadcast(&smb_dclocator.sdl_cv);
 249         }
 250 
 251 out:
 252         (void) mutex_unlock(&smb_dclocator.sdl_mtx);
 253 }
 254 
 255 /*
 256  * If domain discovery is running, wait for it to finish.
 257  */
 258 int
 259 smb_ddiscover_wait(void)
 260 {
 261         timestruc_t to;
 262         int rc = 0;
 263 
 264         (void) mutex_lock(&smb_dclocator.sdl_mtx);
 265 
 266         if (smb_dclocator.sdl_locate) {
 267                 to.tv_sec = SMB_DCLOCATOR_TIMEOUT;
 268                 to.tv_nsec = 0;
 269                 rc = cond_reltimedwait(&smb_dclocator.sdl_cv,
 270                     &smb_dclocator.sdl_mtx, &to);
 271         }
 272 
 273         (void) mutex_unlock(&smb_dclocator.sdl_mtx);
 274 
 275         return (rc);
 276 }
 277 
 278 
 279 /*
 280  * ==========================================================
 281  * DC discovery functions
 282  * ==========================================================
 283  */
 284 
 285 /*
 286  * This is the domain and DC discovery service: it gets woken up whenever
 287  * there is need to locate a domain controller.
 288  *
 289  * Upon success, the SMB domain cache will be populated with the discovered
 290  * DC and domain info.
 291  */
 292 /*ARGSUSED*/
 293 static void *
 294 smb_ddiscover_service(void *arg)
 295 {
 296         smb_domainex_t dxi;
 297         smb_dclocator_t *sdl = arg;
 298         uint32_t status;
 299         boolean_t bad_dc;
 300         boolean_t cfg_chg;
 301 
 302         for (;;) {
 303                 /*
 304                  * Wait to be signaled for work by one of:
 305                  * smb_locate_dc(), smb_ddiscover_refresh(),
 306                  * smb_ddiscover_bad_dc()
 307                  */
 308                 syslog(LOG_DEBUG, "smb_ddiscover_service waiting");
 309 
 310                 (void) mutex_lock(&sdl->sdl_mtx);
 311                 while (!sdl->sdl_locate)
 312                         (void) cond_wait(&sdl->sdl_cv,
 313                             &sdl->sdl_mtx);
 314 
 315                 if (!smb_config_getbool(SMB_CI_DOMAIN_MEMB)) {
 316                         sdl->sdl_status = NT_STATUS_INVALID_SERVER_STATE;
 317                         syslog(LOG_DEBUG, "smb_ddiscover_service: "
 318                             "not a domain member");
 319                         goto wait_again;
 320                 }
 321 
 322                 /*
 323                  * Want to know if these change below.
 324                  * Note: mutex held here
 325                  */
 326         find_again:
 327                 bad_dc = sdl->sdl_bad_dc;
 328                 sdl->sdl_bad_dc = B_FALSE;
 329                 if (bad_dc) {
 330                         /*
 331                          * Need to clear the current DC name or
 332                          * ddiscover_bad_dc will keep setting bad_dc
 333                          */
 334                         sdl->sdl_dci.dc_name[0] = '\0';
 335                 }
 336                 cfg_chg = sdl->sdl_cfg_chg;
 337                 sdl->sdl_cfg_chg = B_FALSE;
 338 
 339                 (void) mutex_unlock(&sdl->sdl_mtx);
 340 
 341                 syslog(LOG_DEBUG, "smb_ddiscover_service running "
 342                     "cfg_chg=%d bad_dc=%d", (int)cfg_chg, (int)bad_dc);
 343 
 344                 /*
 345                  * Clear the cached DC now so that we'll ask idmap again.
 346                  * If our current DC gave us errors, force rediscovery.
 347                  */
 348                 smb_ads_refresh(bad_dc);
 349 
 350                 /*
 351                  * Search for the DC, save the result.
 352                  */
 353                 bzero(&dxi, sizeof (dxi));
 354                 status = smb_ddiscover_main(sdl->sdl_domain, &dxi);
 355                 if (status == 0)
 356                         smb_domain_save();
 357                 (void) mutex_lock(&sdl->sdl_mtx);
 358                 sdl->sdl_status = status;
 359                 if (status == 0)
 360                         sdl->sdl_dci = dxi.d_dci;
 361 
 362                 /*
 363                  * Run again if either of cfg_chg or bad_dc
 364                  * was turned on during smb_ddiscover_main().
 365                  * Note: mutex held here.
 366                  */
 367                 if (sdl->sdl_bad_dc) {
 368                         syslog(LOG_DEBUG, "smb_ddiscover_service "
 369                             "restart because bad_dc was set");
 370                         goto find_again;
 371                 }
 372                 if (sdl->sdl_cfg_chg) {
 373                         syslog(LOG_DEBUG, "smb_ddiscover_service "
 374                             "restart because cfg_chg was set");
 375                         goto find_again;
 376                 }
 377 
 378         wait_again:
 379                 sdl->sdl_locate = B_FALSE;
 380                 sdl->sdl_bad_dc = B_FALSE;
 381                 sdl->sdl_cfg_chg = B_FALSE;
 382                 (void) cond_broadcast(&sdl->sdl_cv);
 383                 (void) mutex_unlock(&sdl->sdl_mtx);
 384         }
 385 
 386         /*NOTREACHED*/
 387         return (NULL);
 388 }
 389 
 390 /*
 391  * Discovers a domain controller for the specified domain via DNS.
 392  * After the domain controller is discovered successfully primary and
 393  * trusted domain infromation will be queried using RPC queries.
 394  *
 395  * Caller should zero out *dxi before calling, and after a
 396  * successful return should call:  smb_domain_save()
 397  */
 398 uint32_t
 399 smb_ddiscover_main(char *domain, smb_domainex_t *dxi)
 400 {
 401         uint32_t status;
 402 
 403         if (domain[0] == '\0') {
 404                 syslog(LOG_DEBUG, "smb_ddiscover_main NULL domain");
 405                 return (NT_STATUS_INTERNAL_ERROR);
 406         }
 407 
 408         if (smb_domain_start_update() != SMB_DOMAIN_SUCCESS) {
 409                 syslog(LOG_DEBUG, "smb_ddiscover_main can't get lock");
 410                 return (NT_STATUS_INTERNAL_ERROR);
 411         }
 412 
 413         status = smb_ads_lookup_msdcs(domain, &dxi->d_dci);
 414         if (status != 0) {
 415                 syslog(LOG_DEBUG, "smb_ddiscover_main can't find DC (%s)",
 416                     xlate_nt_status(status));
 417                 goto out;
 418         }
 419 
 420         status = smb_ddiscover_qinfo(domain, dxi->d_dci.dc_name, dxi);
 421         if (status != 0) {
 422                 syslog(LOG_DEBUG,
 423                     "smb_ddiscover_main can't get domain info (%s)",
 424                     xlate_nt_status(status));
 425                 goto out;
 426         }
 427 
 428         smb_domain_update(dxi);
 429 
 430 out:
 431         smb_domain_end_update();
 432 
 433         /* Don't need the trusted domain list anymore. */
 434         smb_domainex_free(dxi);
 435 
 436         return (status);
 437 }
 438 
 439 /*
 440  * Obtain primary and trusted domain information using LSA queries.
 441  *
 442  * domain - either NetBIOS or fully-qualified domain name
 443  */
 444 static uint32_t
 445 smb_ddiscover_qinfo(char *domain, char *server, smb_domainex_t *dxi)
 446 {
 447         uint32_t ret, tmp;
 448 
 449         /* If we must return failure, use this first one. */
 450         ret = lsa_query_dns_domain_info(server, domain, &dxi->d_primary);
 451         if (ret == NT_STATUS_SUCCESS)
 452                 goto success;
 453         tmp = smb_ddiscover_use_config(domain, dxi);
 454         if (tmp == NT_STATUS_SUCCESS)
 455                 goto success;
 456         tmp = lsa_query_primary_domain_info(server, domain, &dxi->d_primary);
 457         if (tmp == NT_STATUS_SUCCESS)
 458                 goto success;
 459 
 460         /* All of the above failed. */
 461         return (ret);
 462 
 463 success:
 464         smb_ddiscover_enum_trusted(domain, server, dxi);
 465         return (NT_STATUS_SUCCESS);
 466 }
 467 
 468 /*
 469  * Obtain trusted domains information using LSA queries.
 470  *
 471  * domain - either NetBIOS or fully-qualified domain name.
 472  */
 473 static void
 474 smb_ddiscover_enum_trusted(char *domain, char *server, smb_domainex_t *dxi)
 475 {
 476         smb_trusted_domains_t *list;
 477         uint32_t status;
 478 
 479         list = &dxi->d_trusted;
 480         status = lsa_enum_trusted_domains_ex(server, domain, list);
 481         if (status != NT_STATUS_SUCCESS)
 482                 (void) lsa_enum_trusted_domains(server, domain, list);
 483 }
 484 
 485 /*
 486  * If the domain to be discovered matches the current domain (i.e the
 487  * value of either domain or fqdn configuration), then get the primary
 488  * domain information from SMF.
 489  */
 490 static uint32_t
 491 smb_ddiscover_use_config(char *domain, smb_domainex_t *dxi)
 492 {
 493         boolean_t use;
 494         smb_domain_t *dinfo;
 495 
 496         dinfo = &dxi->d_primary;
 497         bzero(dinfo, sizeof (smb_domain_t));
 498 
 499         if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
 500                 return (NT_STATUS_UNSUCCESSFUL);
 501 
 502         smb_config_getdomaininfo(dinfo->di_nbname, dinfo->di_fqname,
 503             NULL, NULL, NULL);
 504 
 505         if (SMB_IS_FQDN(domain))
 506                 use = (smb_strcasecmp(dinfo->di_fqname, domain, 0) == 0);
 507         else
 508                 use = (smb_strcasecmp(dinfo->di_nbname, domain, 0) == 0);
 509 
 510         if (use)
 511                 smb_config_getdomaininfo(NULL, NULL, dinfo->di_sid,
 512                     dinfo->di_u.di_dns.ddi_forest,
 513                     dinfo->di_u.di_dns.ddi_guid);
 514 
 515         return ((use) ? NT_STATUS_SUCCESS : NT_STATUS_UNSUCCESSFUL);
 516 }
 517 
 518 static void
 519 smb_domainex_free(smb_domainex_t *dxi)
 520 {
 521         free(dxi->d_trusted.td_domains);
 522         dxi->d_trusted.td_domains = NULL;
 523 }
 524 
 525 static void
 526 smb_set_krb5_realm(char *domain)
 527 {
 528         static char realm[MAXHOSTNAMELEN];
 529 
 530         if (domain == NULL || domain[0] == '\0') {
 531                 (void) unsetenv("KRB5_DEFAULT_REALM");
 532                 return;
 533         }
 534 
 535         /* In case krb5.conf is not configured, set the default realm. */
 536         (void) strlcpy(realm, domain, sizeof (realm));
 537         (void) smb_strupr(realm);
 538 
 539         (void) setenv("KRB5_DEFAULT_REALM", realm, 1);
 540 }