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