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