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