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 } 461