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