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
mlsvc_netlogon(char * server,char * domain)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
mlsvc_join(smb_joininfo_t * info,smb_joinres_t * res)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
mlsvc_join_rpc(smb_domainex_t * dxi,char * admin_user,char * admin_pw,char * machine_name,char * machine_pw)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
mlsvc_join_noauth(smb_domainex_t * dxi,char * machine_name,char * machine_pw)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
mlsvc_disconnect(const char * server)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
ndr_is_admin(ndr_xa_t * xa)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
ndr_is_poweruser(ndr_xa_t * xa)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
ndr_native_os(ndr_xa_t * xa)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