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 2017 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         /*
  94          * We need the smb_ddiscover_service to run on startup,
  95          * so it will enter smb_ddiscover_main() and put the
  96          * SMB "domain cache" into "updating" state so clients
  97          * trying to logon will wait while we're finding a DC.
  98          */
  99         smb_dclocator.sdl_locate = B_TRUE;
 100 
 101         (void) pthread_attr_init(&tattr);
 102         (void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
 103         rc = pthread_create(&smb_dclocator_thr, &tattr,
 104             smb_ddiscover_service, &smb_dclocator);
 105         (void) pthread_attr_destroy(&tattr);
 106         return (rc);
 107 }
 108 
 109 /*
 110  * This is the entry point for discovering a domain controller for the
 111  * specified domain.  Called during join domain, and then periodically
 112  * by smbd_dc_update (the "DC monitor" thread).
 113  *
 114  * The actual work of discovering a DC is handled by DC locator thread.
 115  * All we do here is signal the request and wait for a DC or a timeout.
 116  *
 117  * Input parameters:
 118  *  domain - domain to be discovered (can either be NetBIOS or DNS domain)
 119  *
 120  * Output parameter:
 121  *  dp - on success, dp will be filled with the discovered DC and domain
 122  *       information.
 123  *
 124  * Returns B_TRUE if the DC/domain info is available.
 125  */
 126 boolean_t
 127 smb_locate_dc(char *domain, smb_domainex_t *dp)
 128 {
 129         int rc;
 130         boolean_t rv;
 131         timestruc_t to;
 132         smb_domainex_t domain_info;
 133 
 134         if (domain == NULL || *domain == '\0') {
 135                 syslog(LOG_DEBUG, "smb_locate_dc NULL dom");
 136                 smb_set_krb5_realm(NULL);
 137                 return (B_FALSE);
 138         }
 139 
 140         (void) mutex_lock(&smb_dclocator.sdl_mtx);
 141 
 142         if (strcmp(smb_dclocator.sdl_domain, domain)) {
 143                 (void) strlcpy(smb_dclocator.sdl_domain, domain,
 144                     sizeof (smb_dclocator.sdl_domain));
 145                 smb_dclocator.sdl_cfg_chg = B_TRUE;
 146                 syslog(LOG_DEBUG, "smb_locate_dc new dom=%s", domain);
 147                 smb_set_krb5_realm(domain);
 148         }
 149 
 150         if (!smb_dclocator.sdl_locate) {
 151                 smb_dclocator.sdl_locate = B_TRUE;
 152                 (void) cond_broadcast(&smb_dclocator.sdl_cv);
 153         }
 154 
 155         while (smb_dclocator.sdl_locate) {
 156                 to.tv_sec = SMB_DCLOCATOR_TIMEOUT;
 157                 to.tv_nsec = 0;
 158                 rc = cond_reltimedwait(&smb_dclocator.sdl_cv,
 159                     &smb_dclocator.sdl_mtx, &to);
 160 
 161                 if (rc == ETIME) {
 162                         syslog(LOG_NOTICE, "smb_locate_dc timeout");
 163                         rv = B_FALSE;
 164                         goto out;
 165                 }
 166         }
 167         if (smb_dclocator.sdl_status != 0) {
 168                 syslog(LOG_NOTICE, "smb_locate_dc status 0x%x",
 169                     smb_dclocator.sdl_status);
 170                 rv = B_FALSE;
 171                 goto out;
 172         }
 173 
 174         if (dp == NULL)
 175                 dp = &domain_info;
 176         rv = smb_domain_getinfo(dp);
 177 
 178 out:
 179         (void) mutex_unlock(&smb_dclocator.sdl_mtx);
 180 
 181         return (rv);
 182 }
 183 
 184 /*
 185  * Tell the domain discovery service to run again now,
 186  * and assume changed configuration (i.e. a new DC).
 187  * Like the first part of smb_locate_dc().
 188  *
 189  * Note: This is called from the service refresh handler
 190  * and the door handler to tell the ddiscover thread to
 191  * request the new DC from idmap.  Therefore, we must not
 192  * trigger a new idmap discovery run from here, or that
 193  * would start a ping-pong match.
 194  */
 195 /* ARGSUSED */
 196 void
 197 smb_ddiscover_refresh()
 198 {
 199 
 200         (void) mutex_lock(&smb_dclocator.sdl_mtx);
 201 
 202         if (smb_dclocator.sdl_cfg_chg == B_FALSE) {
 203                 smb_dclocator.sdl_cfg_chg = B_TRUE;
 204                 syslog(LOG_DEBUG, "smb_ddiscover_refresh set cfg changed");
 205         }
 206         if (!smb_dclocator.sdl_locate) {
 207                 smb_dclocator.sdl_locate = B_TRUE;
 208                 (void) cond_broadcast(&smb_dclocator.sdl_cv);
 209         }
 210 
 211         (void) mutex_unlock(&smb_dclocator.sdl_mtx);
 212 }
 213 
 214 /*
 215  * Called by our client-side threads after they fail to connect to
 216  * the DC given to them by smb_locate_dc().  This is often called
 217  * after some delay, because the connection timeout delays these
 218  * threads for a while, so it's quite common that the DC locator
 219  * service has already started looking for a new DC.  These late
 220  * notifications should not continually restart the DC locator.
 221  */
 222 void
 223 smb_ddiscover_bad_dc(char *bad_dc)
 224 {
 225 
 226         assert(bad_dc[0] != '\0');
 227 
 228         (void) mutex_lock(&smb_dclocator.sdl_mtx);
 229 
 230         syslog(LOG_DEBUG, "smb_ddiscover_bad_dc, cur=%s, bad=%s",
 231             smb_dclocator.sdl_dci.dc_name, bad_dc);
 232 
 233         if (strcmp(smb_dclocator.sdl_dci.dc_name, bad_dc)) {
 234                 /*
 235                  * The "bad" DC is no longer the current one.
 236                  * Probably a late "bad DC" report.
 237                  */
 238                 goto out;
 239         }
 240         if (smb_dclocator.sdl_bad_dc) {
 241                 /* Someone already marked the current DC as "bad". */
 242                 syslog(LOG_DEBUG, "smb_ddiscover_bad_dc repeat");
 243                 goto out;
 244         }
 245 
 246         /*
 247          * Mark the current DC as "bad" and let the DC Locator
 248          * run again if it's not already.
 249          */
 250         syslog(LOG_INFO, "smb_ddiscover, bad DC: %s", bad_dc);
 251         smb_dclocator.sdl_bad_dc = B_TRUE;
 252         smb_domain_bad_dc();
 253 
 254         /* In-line smb_ddiscover_kick */
 255         if (!smb_dclocator.sdl_locate) {
 256                 smb_dclocator.sdl_locate = B_TRUE;
 257                 (void) cond_broadcast(&smb_dclocator.sdl_cv);
 258         }
 259 
 260 out:
 261         (void) mutex_unlock(&smb_dclocator.sdl_mtx);
 262 }
 263 
 264 
 265 /*
 266  * ==========================================================
 267  * DC discovery functions
 268  * ==========================================================
 269  */
 270 
 271 /*
 272  * This is the domain and DC discovery service: it gets woken up whenever
 273  * there is need to locate a domain controller.
 274  *
 275  * Upon success, the SMB domain cache will be populated with the discovered
 276  * DC and domain info.
 277  */
 278 /*ARGSUSED*/
 279 static void *
 280 smb_ddiscover_service(void *arg)
 281 {
 282         smb_domainex_t dxi;
 283         smb_dclocator_t *sdl = arg;
 284         uint32_t status;
 285         boolean_t bad_dc;
 286         boolean_t cfg_chg;
 287 
 288         for (;;) {
 289                 /*
 290                  * Wait to be signaled for work by one of:
 291                  * smb_locate_dc(), smb_ddiscover_refresh(),
 292                  * smb_ddiscover_bad_dc()
 293                  */
 294                 syslog(LOG_DEBUG, "smb_ddiscover_service waiting");
 295 
 296                 (void) mutex_lock(&sdl->sdl_mtx);
 297                 while (!sdl->sdl_locate)
 298                         (void) cond_wait(&sdl->sdl_cv,
 299                             &sdl->sdl_mtx);
 300 
 301                 if (!smb_config_getbool(SMB_CI_DOMAIN_MEMB)) {
 302                         sdl->sdl_status = NT_STATUS_INVALID_SERVER_STATE;
 303                         syslog(LOG_DEBUG, "smb_ddiscover_service: "
 304                             "not a domain member");
 305                         goto wait_again;
 306                 }
 307 
 308                 /*
 309                  * Want to know if these change below.
 310                  * Note: mutex held here
 311                  */
 312         find_again:
 313                 bad_dc = sdl->sdl_bad_dc;
 314                 sdl->sdl_bad_dc = B_FALSE;
 315                 if (bad_dc) {
 316                         /*
 317                          * Need to clear the current DC name or
 318                          * ddiscover_bad_dc will keep setting bad_dc
 319                          */
 320                         sdl->sdl_dci.dc_name[0] = '\0';
 321                 }
 322                 cfg_chg = sdl->sdl_cfg_chg;
 323                 sdl->sdl_cfg_chg = B_FALSE;
 324 
 325                 (void) mutex_unlock(&sdl->sdl_mtx);
 326 
 327                 syslog(LOG_DEBUG, "smb_ddiscover_service running "
 328                     "cfg_chg=%d bad_dc=%d", (int)cfg_chg, (int)bad_dc);
 329 
 330                 /*
 331                  * Clear the cached DC now so that we'll ask idmap again.
 332                  * If our current DC gave us errors, force rediscovery.
 333                  */
 334                 smb_ads_refresh(bad_dc);
 335 
 336                 /*
 337                  * Search for the DC, save the result.
 338                  */
 339                 bzero(&dxi, sizeof (dxi));
 340                 status = smb_ddiscover_main(sdl->sdl_domain, &dxi);
 341                 if (status == 0)
 342                         smb_domain_save();
 343 
 344                 (void) mutex_lock(&sdl->sdl_mtx);
 345 
 346                 sdl->sdl_status = status;
 347                 if (status == 0) {
 348                         sdl->sdl_dci = dxi.d_dci;
 349                 } else {
 350                         syslog(LOG_DEBUG, "smb_ddiscover_service "
 351                             "retry after STATUS_%s",
 352                             xlate_nt_status(status));
 353                         (void) sleep(5);
 354                         goto find_again;
 355                 }
 356 
 357                 /*
 358                  * Run again if either of cfg_chg or bad_dc
 359                  * was turned on during smb_ddiscover_main().
 360                  * Note: mutex held here.
 361                  */
 362                 if (sdl->sdl_bad_dc) {
 363                         syslog(LOG_DEBUG, "smb_ddiscover_service "
 364                             "restart because bad_dc was set");
 365                         goto find_again;
 366                 }
 367                 if (sdl->sdl_cfg_chg) {
 368                         syslog(LOG_DEBUG, "smb_ddiscover_service "
 369                             "restart because cfg_chg was set");
 370                         goto find_again;
 371                 }
 372 
 373         wait_again:
 374                 sdl->sdl_locate = B_FALSE;
 375                 sdl->sdl_bad_dc = B_FALSE;
 376                 sdl->sdl_cfg_chg = B_FALSE;
 377                 (void) cond_broadcast(&sdl->sdl_cv);
 378                 (void) mutex_unlock(&sdl->sdl_mtx);
 379         }
 380 
 381         /*NOTREACHED*/
 382         return (NULL);
 383 }
 384 
 385 /*
 386  * Discovers a domain controller for the specified domain via DNS.
 387  * After the domain controller is discovered successfully primary and
 388  * trusted domain infromation will be queried using RPC queries.
 389  *
 390  * Caller should zero out *dxi before calling, and after a
 391  * successful return should call:  smb_domain_save()
 392  */
 393 uint32_t
 394 smb_ddiscover_main(char *domain, smb_domainex_t *dxi)
 395 {
 396         uint32_t status;
 397 
 398         if (domain[0] == '\0') {
 399                 syslog(LOG_DEBUG, "smb_ddiscover_main NULL domain");
 400                 return (NT_STATUS_INTERNAL_ERROR);
 401         }
 402 
 403         status = smb_ads_lookup_msdcs(domain, &dxi->d_dci);
 404         if (status != 0) {
 405                 syslog(LOG_DEBUG, "smb_ddiscover_main can't find DC (%s)",
 406                     xlate_nt_status(status));
 407                 goto out;
 408         }
 409 
 410         status = smb_ddiscover_qinfo(domain, dxi->d_dci.dc_name, dxi);
 411         if (status != 0) {
 412                 syslog(LOG_DEBUG,
 413                     "smb_ddiscover_main can't get domain info (%s)",
 414                     xlate_nt_status(status));
 415                 goto out;
 416         }
 417 
 418         if (smb_domain_start_update() != SMB_DOMAIN_SUCCESS) {
 419                 syslog(LOG_DEBUG, "smb_ddiscover_main can't get lock");
 420                 status = NT_STATUS_INTERNAL_ERROR;
 421         } else {
 422                 smb_domain_update(dxi);
 423                 smb_domain_end_update();
 424         }
 425 
 426 out:
 427         /* Don't need the trusted domain list anymore. */
 428         smb_domainex_free(dxi);
 429 
 430         return (status);
 431 }
 432 
 433 /*
 434  * Obtain primary and trusted domain information using LSA queries.
 435  *
 436  * domain - either NetBIOS or fully-qualified domain name
 437  */
 438 static uint32_t
 439 smb_ddiscover_qinfo(char *domain, char *server, smb_domainex_t *dxi)
 440 {
 441         uint32_t ret, tmp;
 442 
 443         /* If we must return failure, use this first one. */
 444         ret = lsa_query_dns_domain_info(server, domain, &dxi->d_primary);
 445         if (ret == NT_STATUS_SUCCESS)
 446                 goto success;
 447         tmp = smb_ddiscover_use_config(domain, dxi);
 448         if (tmp == NT_STATUS_SUCCESS)
 449                 goto success;
 450         tmp = lsa_query_primary_domain_info(server, domain, &dxi->d_primary);
 451         if (tmp == NT_STATUS_SUCCESS)
 452                 goto success;
 453 
 454         /* All of the above failed. */
 455         return (ret);
 456 
 457 success:
 458         smb_ddiscover_enum_trusted(domain, server, dxi);
 459         return (NT_STATUS_SUCCESS);
 460 }
 461 
 462 /*
 463  * Obtain trusted domains information using LSA queries.
 464  *
 465  * domain - either NetBIOS or fully-qualified domain name.
 466  */
 467 static void
 468 smb_ddiscover_enum_trusted(char *domain, char *server, smb_domainex_t *dxi)
 469 {
 470         smb_trusted_domains_t *list;
 471         uint32_t status;
 472 
 473         list = &dxi->d_trusted;
 474         status = lsa_enum_trusted_domains_ex(server, domain, list);
 475         if (status != NT_STATUS_SUCCESS)
 476                 (void) lsa_enum_trusted_domains(server, domain, list);
 477 }
 478 
 479 /*
 480  * If the domain to be discovered matches the current domain (i.e the
 481  * value of either domain or fqdn configuration), then get the primary
 482  * domain information from SMF.
 483  */
 484 static uint32_t
 485 smb_ddiscover_use_config(char *domain, smb_domainex_t *dxi)
 486 {
 487         boolean_t use;
 488         smb_domain_t *dinfo;
 489 
 490         dinfo = &dxi->d_primary;
 491         bzero(dinfo, sizeof (smb_domain_t));
 492 
 493         if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
 494                 return (NT_STATUS_UNSUCCESSFUL);
 495 
 496         smb_config_getdomaininfo(dinfo->di_nbname, dinfo->di_fqname,
 497             NULL, NULL, NULL);
 498 
 499         if (SMB_IS_FQDN(domain))
 500                 use = (smb_strcasecmp(dinfo->di_fqname, domain, 0) == 0);
 501         else
 502                 use = (smb_strcasecmp(dinfo->di_nbname, domain, 0) == 0);
 503 
 504         if (use)
 505                 smb_config_getdomaininfo(NULL, NULL, dinfo->di_sid,
 506                     dinfo->di_u.di_dns.ddi_forest,
 507                     dinfo->di_u.di_dns.ddi_guid);
 508 
 509         return ((use) ? NT_STATUS_SUCCESS : NT_STATUS_UNSUCCESSFUL);
 510 }
 511 
 512 static void
 513 smb_domainex_free(smb_domainex_t *dxi)
 514 {
 515         free(dxi->d_trusted.td_domains);
 516         dxi->d_trusted.td_domains = NULL;
 517 }
 518 
 519 static void
 520 smb_set_krb5_realm(char *domain)
 521 {
 522         static char realm[MAXHOSTNAMELEN];
 523 
 524         if (domain == NULL || domain[0] == '\0') {
 525                 (void) unsetenv("KRB5_DEFAULT_REALM");
 526                 return;
 527         }
 528 
 529         /* In case krb5.conf is not configured, set the default realm. */
 530         (void) strlcpy(realm, domain, sizeof (realm));
 531         (void) smb_strupr(realm);
 532 
 533         (void) setenv("KRB5_DEFAULT_REALM", realm, 1);
 534 }