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