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  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
  23  * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
  24  */
  25 
  26 /*
  27  * Utility functions to support the RPC interface library.
  28  */
  29 
  30 #include <stdio.h>
  31 #include <stdarg.h>
  32 #include <strings.h>
  33 #include <unistd.h>
  34 #include <netdb.h>
  35 #include <stdlib.h>
  36 #include <sys/time.h>
  37 #include <sys/systm.h>
  38 #include <note.h>
  39 #include <syslog.h>
  40 
  41 #include <smbsrv/libsmb.h>
  42 #include <smbsrv/libsmbns.h>
  43 #include <smbsrv/libmlsvc.h>
  44 #include <smbsrv/ntaccess.h>
  45 #include <smbsrv/smbinfo.h>
  46 #include <smbsrv/netrauth.h>
  47 #include <libsmbrdr.h>
  48 #include <lsalib.h>
  49 #include <samlib.h>
  50 #include <mlsvc.h>
  51 
  52 static DWORD
  53 mlsvc_join_rpc(smb_domainex_t *dxi,
  54         char *admin_user, char *admin_pw,
  55         char *machine_name, char *machine_pw);
  56 static DWORD
  57 mlsvc_join_noauth(smb_domainex_t *dxi,
  58         char *machine_name, char *machine_pw);
  59 
  60 
  61 DWORD
  62 mlsvc_netlogon(char *server, char *domain)
  63 {
  64         mlsvc_handle_t netr_handle;
  65         DWORD status;
  66 
  67         status = netr_open(server, domain, &netr_handle);
  68         if (status != 0) {
  69                 syslog(LOG_NOTICE, "Failed to connect to %s "
  70                     "for domain %s (%s)", server, domain,
  71                     xlate_nt_status(status));
  72                 return (status);
  73         }
  74 
  75         status = netlogon_auth(server, &netr_handle, NETR_FLG_INIT);
  76         if (status != NT_STATUS_SUCCESS) {
  77                 syslog(LOG_NOTICE, "Failed to establish NETLOGON "
  78                     "credential chain with DC: %s (%s)", server,
  79                     xlate_nt_status(status));
  80                 syslog(LOG_NOTICE, "The machine account information on the "
  81                     "domain controller does not match the local storage.");
  82                 syslog(LOG_NOTICE, "To correct this, use 'smbadm join'");
  83         }
  84         (void) netr_close(&netr_handle);
  85 
  86         return (status);
  87 }
  88 
  89 /*
  90  * Join the specified domain.  The method varies depending on whether
  91  * we're using "secure join" (using an administrative account to join)
  92  * or "unsecure join" (using a pre-created machine account).  In the
  93  * latter case, the machine account is created "by hand" before this
  94  * machine attempts to join, and we just change the password from the
  95  * (weak) default password for a new machine account to a random one.
  96  *
  97  * Returns NT status codes.
  98  */
  99 void
 100 mlsvc_join(smb_joininfo_t *info, smb_joinres_t *res)
 101 {
 102         static unsigned char zero_hash[SMBAUTH_HASH_SZ];
 103         char machine_name[SMB_SAMACCT_MAXLEN];
 104         char machine_pw[NETR_MACHINE_ACCT_PASSWD_MAX];
 105         unsigned char passwd_hash[SMBAUTH_HASH_SZ];
 106         smb_domainex_t dxi;
 107         smb_domain_t *di = &dxi.d_primary;
 108         DWORD status;
 109         int rc;
 110 
 111         bzero(&dxi, sizeof (dxi));
 112 
 113         /*
 114          * Domain join support: AD (Kerberos+LDAP) or MS-RPC?
 115          */
 116         boolean_t ads_enabled = smb_config_get_ads_enable();
 117 
 118         if (smb_getsamaccount(machine_name, sizeof (machine_name)) != 0) {
 119                 res->status = NT_STATUS_INVALID_COMPUTER_NAME;
 120                 return;
 121         }
 122 
 123         (void) smb_gen_random_passwd(machine_pw, sizeof (machine_pw));
 124 
 125         /*
 126          * Ensure that any previous membership of this domain has
 127          * been cleared from the environment before we start. This
 128          * will ensure that we don't attempt a NETLOGON_SAMLOGON
 129          * when attempting to find the PDC.
 130          */
 131         (void) smb_config_setbool(SMB_CI_DOMAIN_MEMB, B_FALSE);
 132 
 133         if (info->domain_username[0] != '\0') {
 134                 (void) smb_auth_ntlm_hash(info->domain_passwd, passwd_hash);
 135                 smb_ipc_set(info->domain_username, passwd_hash);
 136         } else {
 137                 smb_ipc_set(MLSVC_ANON_USER, zero_hash);
 138         }
 139 
 140         /*
 141          * Tentatively set the idmap domain to the one we're joining,
 142          * so that the DC locator in idmap knows what to look for.
 143          * Ditto the SMB server domain.
 144          */
 145         if (smb_config_set_idmap_domain(info->domain_name) != 0)
 146                 syslog(LOG_NOTICE, "Failed to set idmap domain name");
 147         if (smb_config_refresh_idmap() != 0)
 148                 syslog(LOG_NOTICE, "Failed to refresh idmap service");
 149 
 150         /* Clear DNS local (ADS) lookup cache. */
 151         smb_ads_refresh(B_FALSE);
 152 
 153         /*
 154          * Locate a DC for this domain.  Intentionally bypass the
 155          * ddiscover service here because we're still joining.
 156          * This also allows better reporting of any failures.
 157          */
 158         status = smb_ads_lookup_msdcs(info->domain_name, &dxi.d_dci);
 159         if (status != NT_STATUS_SUCCESS) {
 160                 syslog(LOG_ERR,
 161                     "smbd: failed to locate AD server for domain %s (%s)",
 162                     info->domain_name, xlate_nt_status(status));
 163                 goto out;
 164         }
 165 
 166         /*
 167          * Found a DC.  Report what we found along with the return status
 168          * so that admin will know which AD server we were talking to.
 169          */
 170         (void) strlcpy(res->dc_name, dxi.d_dci.dc_name, MAXHOSTNAMELEN);
 171         syslog(LOG_INFO, "smbd: found AD server %s", dxi.d_dci.dc_name);
 172 
 173         /*
 174          * Domain discovery needs to authenticate with the AD server.
 175          * Disconnect any existing connection with the domain controller
 176          * to make sure we won't use any prior authentication context
 177          * our redirector might have.
 178          */
 179         mlsvc_disconnect(dxi.d_dci.dc_name);
 180 
 181         /*
 182          * Get the domain policy info (domain SID etc).
 183          * Here too, bypass the smb_ddiscover_service.
 184          */
 185         status = smb_ddiscover_main(info->domain_name, &dxi);
 186         if (status != NT_STATUS_SUCCESS) {
 187                 syslog(LOG_ERR,
 188                     "smbd: failed getting domain info for %s (%s)",
 189                     info->domain_name, xlate_nt_status(status));
 190                 goto out;
 191         }
 192         /*
 193          * After a successful smbd_ddiscover_main() call
 194          * we should call smb_domain_save() to update the
 195          * data shown by smbadm list.  Do that at the end,
 196          * only if all goes well with joining the domain.
 197          */
 198 
 199         /*
 200          * Create or update our machine account on the DC.
 201          * A non-null user means we do "secure join".
 202          */
 203         if (info->domain_username[0] != '\0') {
 204                 /*
 205                  * If enabled, try to join using AD Services.
 206                  */
 207                 status = NT_STATUS_UNSUCCESSFUL;
 208                 if (ads_enabled) {
 209                         res->join_err = smb_ads_join(di->di_fqname,
 210                             info->domain_username, info->domain_passwd,
 211                             machine_pw);
 212                         if (res->join_err == SMB_ADS_SUCCESS) {
 213                                 status = NT_STATUS_SUCCESS;
 214                         }
 215                 } else {
 216                         syslog(LOG_DEBUG, "use_ads=false (do RPC join)");
 217 
 218                         /*
 219                          * If ADS was disabled, join using RPC.
 220                          */
 221                         status = mlsvc_join_rpc(&dxi,
 222                             info->domain_username,
 223                             info->domain_passwd,
 224                             machine_name, machine_pw);
 225                 }
 226 
 227         } else {
 228                 /*
 229                  * Doing "Unsecure join" (pre-created account)
 230                  */
 231                 status = mlsvc_join_noauth(&dxi, machine_name, machine_pw);
 232         }
 233 
 234         if (status != NT_STATUS_SUCCESS)
 235                 goto out;
 236 
 237         /*
 238          * Make sure we can authenticate using the
 239          * (new, or updated) machine account.
 240          */
 241         (void) smb_auth_ntlm_hash(machine_pw, passwd_hash);
 242         smb_ipc_set(machine_name, passwd_hash);
 243         rc = smbrdr_logon(dxi.d_dci.dc_name, di->di_nbname, machine_name);
 244         if (rc != 0) {
 245                 syslog(LOG_NOTICE, "Authenticate with "
 246                     "new/updated machine account: %s",
 247                     strerror(rc));
 248                 res->join_err = SMB_ADJOIN_ERR_AUTH_NETLOGON;
 249                 status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
 250                 goto out;
 251         }
 252 
 253         /*
 254          * Store the new machine account password, and
 255          * SMB_CI_DOMAIN_MEMB etc.
 256          */
 257         rc = smb_setdomainprops(NULL, dxi.d_dci.dc_name, machine_pw);
 258         if (rc != 0) {
 259                 syslog(LOG_NOTICE,
 260                     "Failed to save machine account password");
 261                 res->join_err = SMB_ADJOIN_ERR_STORE_PROPS;
 262                 status = NT_STATUS_INTERNAL_DB_ERROR;
 263                 goto out;
 264         }
 265 
 266         /*
 267          * Update idmap config?
 268          * Already set the domain_name above.
 269          */
 270 
 271         /*
 272          * Save the SMB server config.  Sets: SMB_CI_DOMAIN_*
 273          * Should unify SMB vs idmap configs.
 274          */
 275         smb_config_setdomaininfo(di->di_nbname, di->di_fqname,
 276             di->di_sid,
 277             di->di_u.di_dns.ddi_forest,
 278             di->di_u.di_dns.ddi_guid);
 279         smb_ipc_commit();
 280         smb_domain_save();
 281 
 282         status = 0;
 283 
 284 out:
 285 
 286         if (status != 0) {
 287                 /*
 288                  * Undo the tentative domain settings.
 289                  */
 290                 (void) smb_config_set_idmap_domain("");
 291                 (void) smb_config_refresh_idmap();
 292                 smb_ipc_rollback();
 293         }
 294 
 295         /* Avoid leaving cleartext passwords around. */
 296         bzero(machine_pw, sizeof (machine_pw));
 297         bzero(passwd_hash, sizeof (passwd_hash));
 298 
 299         res->status = status;
 300 }
 301 
 302 static DWORD
 303 mlsvc_join_rpc(smb_domainex_t *dxi,
 304         char *admin_user, char *admin_pw,
 305         char *machine_name,  char *machine_pw)
 306 {
 307         mlsvc_handle_t samr_handle;
 308         mlsvc_handle_t domain_handle;
 309         mlsvc_handle_t user_handle;
 310         smb_account_t ainfo;
 311         char *server = dxi->d_dci.dc_name;
 312         smb_domain_t *di = &dxi->d_primary;
 313         DWORD account_flags;
 314         DWORD rid;
 315         DWORD status;
 316         int rc;
 317 
 318         /* Caller did smb_ipc_set() so we don't need the pw for now. */
 319         _NOTE(ARGUNUSED(admin_pw));
 320 
 321         rc = samr_open(server, di->di_nbname, admin_user,
 322             MAXIMUM_ALLOWED, &samr_handle);
 323         if (rc != 0) {
 324                 syslog(LOG_NOTICE, "sam_connect to server %s failed", server);
 325                 return (RPC_NT_SERVER_UNAVAILABLE);
 326         }
 327         /* have samr_handle */
 328 
 329         status = samr_open_domain(&samr_handle, MAXIMUM_ALLOWED,
 330             (struct samr_sid *)di->di_binsid, &domain_handle);
 331         if (status != NT_STATUS_SUCCESS)
 332                 goto out_samr_handle;
 333         /* have domain_handle */
 334 
 335         account_flags = SAMR_AF_WORKSTATION_TRUST_ACCOUNT;
 336         status = samr_create_user(&domain_handle, machine_name,
 337             account_flags, &rid, &user_handle);
 338         if (status == NT_STATUS_USER_EXISTS) {
 339                 status = samr_lookup_domain_names(&domain_handle,
 340                     machine_name, &ainfo);
 341                 if (status != NT_STATUS_SUCCESS)
 342                         goto out_domain_handle;
 343                 status = samr_open_user(&domain_handle, MAXIMUM_ALLOWED,
 344                     ainfo.a_rid, &user_handle);
 345         }
 346         if (status != NT_STATUS_SUCCESS) {
 347                 syslog(LOG_NOTICE,
 348                     "smbd: failed to open machine account (%s)",
 349                     xlate_nt_status(status));
 350                 goto out_domain_handle;
 351         }
 352 
 353         /*
 354          * The account exists, and we have user_handle open
 355          * on that account.  Set the password and flags.
 356          */
 357 
 358         status = netr_set_user_password(&user_handle, machine_pw);
 359         if (status != NT_STATUS_SUCCESS) {
 360                 syslog(LOG_NOTICE,
 361                     "smbd: failed to set machine account password (%s)",
 362                     xlate_nt_status(status));
 363                 goto out_user_handle;
 364         }
 365 
 366         account_flags |= SAMR_AF_DONT_EXPIRE_PASSWD;
 367         status = netr_set_user_control(&user_handle, account_flags);
 368         if (status != NT_STATUS_SUCCESS) {
 369                 syslog(LOG_NOTICE,
 370                     "Set machine account control flags: %s",
 371                     xlate_nt_status(status));
 372                 goto out_user_handle;
 373         }
 374 
 375 out_user_handle:
 376         (void) samr_close_handle(&user_handle);
 377 out_domain_handle:
 378         (void) samr_close_handle(&domain_handle);
 379 out_samr_handle:
 380         (void) samr_close_handle(&samr_handle);
 381 
 382         return (status);
 383 }
 384 
 385 /*
 386  * Doing "Unsecure join" (using a pre-created machine account).
 387  * All we need to do is change the password from the default
 388  * to a random string.
 389  *
 390  * Note: this is a work in progres.  Nexenta issue 11960
 391  * (allow joining an AD domain using a pre-created computer account)
 392  * It turns out that to change the machine account password,
 393  * we need to use a different RPC call, performed over the
 394  * NetLogon secure channel.  (See netr_server_password_set2)
 395  */
 396 static DWORD
 397 mlsvc_join_noauth(smb_domainex_t *dxi,
 398         char *machine_name, char *machine_pw)
 399 {
 400         char old_pw[SMB_SAMACCT_MAXLEN];
 401         DWORD status;
 402 
 403         /*
 404          * Compose the current (default) password for the
 405          * pre-created machine account, which is just the
 406          * account name in lower case, truncated to 14
 407          * characters.
 408          */
 409         if (smb_gethostname(old_pw, sizeof (old_pw), SMB_CASE_LOWER) != 0)
 410                 return (NT_STATUS_INTERNAL_ERROR);
 411         old_pw[14] = '\0';
 412 
 413         status = netr_change_password(dxi->d_dci.dc_name, machine_name,
 414             old_pw, machine_pw);
 415         if (status != NT_STATUS_SUCCESS) {
 416                 syslog(LOG_NOTICE,
 417                     "Change machine account password: %s",
 418                     xlate_nt_status(status));
 419         }
 420         return (status);
 421 }
 422 
 423 void
 424 mlsvc_disconnect(const char *server)
 425 {
 426         smbrdr_disconnect(server);
 427 }
 428 
 429 /*
 430  * A few more helper functions for RPC services.
 431  */
 432 
 433 /*
 434  * Check whether or not the specified user has administrator privileges,
 435  * i.e. is a member of Domain Admins or Administrators.
 436  * Returns true if the user is an administrator, otherwise returns false.
 437  */
 438 boolean_t
 439 ndr_is_admin(ndr_xa_t *xa)
 440 {
 441         smb_netuserinfo_t *ctx = xa->pipe->np_user;
 442 
 443         return (ctx->ui_flags & SMB_ATF_ADMIN);
 444 }
 445 
 446 /*
 447  * Check whether or not the specified user has power-user privileges,
 448  * i.e. is a member of Domain Admins, Administrators or Power Users.
 449  * This is typically required for operations such as managing shares.
 450  * Returns true if the user is a power user, otherwise returns false.
 451  */
 452 boolean_t
 453 ndr_is_poweruser(ndr_xa_t *xa)
 454 {
 455         smb_netuserinfo_t *ctx = xa->pipe->np_user;
 456 
 457         return ((ctx->ui_flags & SMB_ATF_ADMIN) ||
 458             (ctx->ui_flags & SMB_ATF_POWERUSER));
 459 }
 460 
 461 int32_t
 462 ndr_native_os(ndr_xa_t *xa)
 463 {
 464         smb_netuserinfo_t *ctx = xa->pipe->np_user;
 465 
 466         return (ctx->ui_native_os);
 467 }