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 /* 23 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. 24 */ 25 26 #include <syslog.h> 27 #include <synch.h> 28 #include <pthread.h> 29 #include <unistd.h> 30 #include <string.h> 31 #include <strings.h> 32 #include <sys/errno.h> 33 34 #include <smbsrv/libsmb.h> 35 #include <smbsrv/libsmbns.h> 36 #include <smbsrv/libmlsvc.h> 37 #include <smbsrv/smbinfo.h> 38 #include "smbd.h" 39 40 #define SMBD_DC_MONITOR_ATTEMPTS 3 41 #define SMBD_DC_MONITOR_RETRY_INTERVAL 3 /* seconds */ 42 #define SMBD_DC_MONITOR_INTERVAL 60 /* seconds */ 43 44 extern smbd_t smbd; 45 46 static mutex_t smbd_dc_mutex; 47 static cond_t smbd_dc_cv; 48 49 static void *smbd_dc_monitor(void *); 50 static void smbd_dc_update(void); 51 static boolean_t smbd_set_netlogon_cred(void); 52 static int smbd_get_kpasswd_srv(char *, size_t); 53 static uint32_t smbd_join_workgroup(smb_joininfo_t *); 54 static uint32_t smbd_join_domain(smb_joininfo_t *); 55 56 /* 57 * Launch the DC discovery and monitor thread. 58 */ 59 int 60 smbd_dc_monitor_init(void) 61 { 62 pthread_attr_t attr; 63 int rc; 64 65 (void) smb_config_getstr(SMB_CI_ADS_SITE, smbd.s_site, 66 MAXHOSTNAMELEN); 67 (void) smb_config_getip(SMB_CI_DOMAIN_SRV, &smbd.s_pdc); 68 smb_ads_init(); 69 70 if (smbd.s_secmode != SMB_SECMODE_DOMAIN) 71 return (0); 72 73 (void) pthread_attr_init(&attr); 74 (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 75 rc = pthread_create(&smbd.s_dc_monitor_tid, &attr, smbd_dc_monitor, 76 NULL); 77 (void) pthread_attr_destroy(&attr); 78 return (rc); 79 } 80 81 void 82 smbd_dc_monitor_refresh(void) 83 { 84 char site[MAXHOSTNAMELEN]; 85 smb_inaddr_t pdc; 86 87 site[0] = '\0'; 88 bzero(&pdc, sizeof (smb_inaddr_t)); 89 (void) smb_config_getstr(SMB_CI_ADS_SITE, site, MAXHOSTNAMELEN); 90 (void) smb_config_getip(SMB_CI_DOMAIN_SRV, &pdc); 91 92 (void) mutex_lock(&smbd_dc_mutex); 93 94 if ((bcmp(&smbd.s_pdc, &pdc, sizeof (smb_inaddr_t)) != 0) || 95 (smb_strcasecmp(smbd.s_site, site, 0) != 0)) { 96 bcopy(&pdc, &smbd.s_pdc, sizeof (smb_inaddr_t)); 97 (void) strlcpy(smbd.s_site, site, MAXHOSTNAMELEN); 98 smbd.s_pdc_changed = B_TRUE; 99 (void) cond_signal(&smbd_dc_cv); 100 } 101 102 (void) mutex_unlock(&smbd_dc_mutex); 103 } 104 105 /*ARGSUSED*/ 106 static void * 107 smbd_dc_monitor(void *arg) 108 { 109 boolean_t ds_not_responding = B_FALSE; 110 boolean_t ds_cfg_changed = B_FALSE; 111 timestruc_t delay; 112 int i; 113 114 smbd_dc_update(); 115 smbd_online_wait("smbd_dc_monitor"); 116 117 while (smbd_online()) { 118 delay.tv_sec = SMBD_DC_MONITOR_INTERVAL; 119 delay.tv_nsec = 0; 120 121 (void) mutex_lock(&smbd_dc_mutex); 122 (void) cond_reltimedwait(&smbd_dc_cv, &smbd_dc_mutex, &delay); 123 124 if (smbd.s_pdc_changed) { 125 smbd.s_pdc_changed = B_FALSE; 126 ds_cfg_changed = B_TRUE; 127 } 128 129 (void) mutex_unlock(&smbd_dc_mutex); 130 131 for (i = 0; i < SMBD_DC_MONITOR_ATTEMPTS; ++i) { 132 if (dssetup_check_service() == 0) { 133 ds_not_responding = B_FALSE; 134 break; 135 } 136 137 ds_not_responding = B_TRUE; 138 (void) sleep(SMBD_DC_MONITOR_RETRY_INTERVAL); 139 } 140 141 if (ds_not_responding) 142 smb_log(smbd.s_loghd, LOG_NOTICE, 143 "smbd_dc_monitor: domain service not responding"); 144 145 if (ds_not_responding || ds_cfg_changed) { 146 ds_cfg_changed = B_FALSE; 147 smb_ads_refresh(); 148 smbd_dc_update(); 149 } 150 } 151 152 smbd.s_dc_monitor_tid = 0; 153 return (NULL); 154 } 155 156 /* 157 * Locate a domain controller in the current resource domain and Update 158 * the Netlogon credential chain. 159 * 160 * The domain configuration will be updated upon successful DC discovery. 161 */ 162 static void 163 smbd_dc_update(void) 164 { 165 char domain[MAXHOSTNAMELEN]; 166 smb_domainex_t info; 167 smb_domain_t *primary; 168 169 170 if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0) { 171 (void) smb_getdomainname(domain, MAXHOSTNAMELEN); 172 (void) smb_strupr(domain); 173 } 174 175 if (!smb_locate_dc(domain, "", &info)) { 176 smb_log(smbd.s_loghd, LOG_NOTICE, 177 "smbd_dc_update: %s: locate failed", domain); 178 } else { 179 primary = &info.d_primary; 180 181 smb_config_setdomaininfo(primary->di_nbname, 182 primary->di_fqname, 183 primary->di_sid, 184 primary->di_u.di_dns.ddi_forest, 185 primary->di_u.di_dns.ddi_guid); 186 187 smb_log(smbd.s_loghd, LOG_NOTICE, 188 "smbd_dc_update: %s: located %s", domain, info.d_dc); 189 } 190 191 if (smbd_set_netlogon_cred()) { 192 /* 193 * Restart required because the domain changed 194 * or the credential chain setup failed. 195 */ 196 smb_log(smbd.s_loghd, LOG_NOTICE, 197 "smbd_dc_update: %s: smb/server restart required"); 198 199 if (smb_smf_restart_service() != 0) 200 smb_log(smbd.s_loghd, LOG_ERR, 201 "restart failed: run 'svcs -xv smb/server'" 202 " for more information"); 203 } 204 } 205 206 /* 207 * smbd_join 208 * 209 * Joins the specified domain/workgroup. 210 * 211 * If the security mode or domain name is being changed, 212 * the caller must restart the service. 213 */ 214 uint32_t 215 smbd_join(smb_joininfo_t *info) 216 { 217 uint32_t status; 218 219 dssetup_clear_domain_info(); 220 if (info->mode == SMB_SECMODE_WORKGRP) 221 status = smbd_join_workgroup(info); 222 else 223 status = smbd_join_domain(info); 224 225 return (status); 226 } 227 228 /* 229 * smbd_set_netlogon_cred 230 * 231 * If the system is joined to an AD domain via kclient, SMB daemon will need 232 * to establish the NETLOGON credential chain. 233 * 234 * Since the kclient has updated the machine password stored in SMF 235 * repository, the cached ipc_info must be updated accordingly by calling 236 * smb_ipc_commit. 237 * 238 * Due to potential replication delays in a multiple DC environment, the 239 * NETLOGON rpc request must be sent to the DC, to which the KPASSWD request 240 * is sent. If the DC discovered by the SMB daemon is different than the 241 * kpasswd server, the current connection with the DC will be torn down 242 * and a DC discovery process will be triggered to locate the kpasswd 243 * server. 244 * 245 * If joining a new domain, the domain_name property must be set after a 246 * successful credential chain setup. 247 */ 248 static boolean_t 249 smbd_set_netlogon_cred(void) 250 { 251 char kpasswd_srv[MAXHOSTNAMELEN]; 252 char kpasswd_domain[MAXHOSTNAMELEN]; 253 char sam_acct[SMB_SAMACCT_MAXLEN]; 254 char ipc_usr[SMB_USERNAME_MAXLEN]; 255 char *dom; 256 boolean_t new_domain = B_FALSE; 257 smb_domainex_t dxi; 258 smb_domain_t *di; 259 260 if (smb_match_netlogon_seqnum()) 261 return (B_FALSE); 262 263 (void) smb_config_getstr(SMB_CI_KPASSWD_SRV, kpasswd_srv, 264 sizeof (kpasswd_srv)); 265 266 if (*kpasswd_srv == '\0') 267 return (B_FALSE); 268 269 /* 270 * If the domain join initiated by smbadm join CLI is in 271 * progress, don't do anything. 272 */ 273 (void) smb_getsamaccount(sam_acct, sizeof (sam_acct)); 274 smb_ipc_get_user(ipc_usr, SMB_USERNAME_MAXLEN); 275 if (smb_strcasecmp(ipc_usr, sam_acct, 0)) 276 return (B_FALSE); 277 278 di = &dxi.d_primary; 279 if (!smb_domain_getinfo(&dxi)) 280 (void) smb_getfqdomainname(di->di_fqname, MAXHOSTNAMELEN); 281 282 (void) smb_config_getstr(SMB_CI_KPASSWD_DOMAIN, kpasswd_domain, 283 sizeof (kpasswd_domain)); 284 285 if (*kpasswd_domain != '\0' && 286 smb_strcasecmp(kpasswd_domain, di->di_fqname, 0)) { 287 dom = kpasswd_domain; 288 new_domain = B_TRUE; 289 } else { 290 dom = di->di_fqname; 291 } 292 293 /* 294 * DC discovery will be triggered if the domain info is not 295 * currently cached or the SMB daemon has previously discovered a DC 296 * that is different than the kpasswd server. 297 */ 298 if (new_domain || smb_strcasecmp(dxi.d_dc, kpasswd_srv, 0) != 0) { 299 if (*dxi.d_dc != '\0') 300 mlsvc_disconnect(dxi.d_dc); 301 302 if (!smb_locate_dc(dom, kpasswd_srv, &dxi)) { 303 if (!smb_locate_dc(di->di_fqname, "", &dxi)) { 304 smb_ipc_commit(); 305 return (B_FALSE); 306 } 307 } 308 } 309 310 smb_ipc_commit(); 311 if (mlsvc_netlogon(dxi.d_dc, di->di_nbname)) { 312 syslog(LOG_NOTICE, 313 "failed to establish NETLOGON credential chain"); 314 return (B_TRUE); 315 } else { 316 if (new_domain) { 317 smb_config_setdomaininfo(di->di_nbname, di->di_fqname, 318 di->di_sid, 319 di->di_u.di_dns.ddi_forest, 320 di->di_u.di_dns.ddi_guid); 321 (void) smb_config_setstr(SMB_CI_KPASSWD_DOMAIN, ""); 322 } 323 } 324 325 return (new_domain); 326 } 327 328 /* 329 * Retrieve the kpasswd server from krb5.conf. 330 * 331 * Initialization of the locate dc thread. 332 * Returns 0 on success, an error number if thread creation fails. 333 */ 334 static int 335 smbd_get_kpasswd_srv(char *srv, size_t len) 336 { 337 FILE *fp; 338 static char buf[512]; 339 char *p; 340 341 *srv = '\0'; 342 p = getenv("KRB5_CONFIG"); 343 if (p == NULL || *p == '\0') 344 p = "/etc/krb5/krb5.conf"; 345 346 if ((fp = fopen(p, "r")) == NULL) 347 return (-1); 348 349 while (fgets(buf, sizeof (buf), fp)) { 350 351 /* Weed out any comment text */ 352 (void) trim_whitespace(buf); 353 if (*buf == '#') 354 continue; 355 356 if ((p = strstr(buf, "kpasswd_server")) != NULL) { 357 if ((p = strchr(p, '=')) != NULL) { 358 (void) trim_whitespace(++p); 359 (void) strlcpy(srv, p, len); 360 } 361 break; 362 } 363 } 364 365 366 (void) fclose(fp); 367 return ((*srv == '\0') ? -1 : 0); 368 } 369 370 static uint32_t 371 smbd_join_workgroup(smb_joininfo_t *info) 372 { 373 char nb_domain[SMB_PI_MAX_DOMAIN]; 374 375 (void) smb_config_getstr(SMB_CI_DOMAIN_NAME, nb_domain, 376 sizeof (nb_domain)); 377 378 smbd_set_secmode(SMB_SECMODE_WORKGRP); 379 smb_config_setdomaininfo(info->domain_name, "", "", "", ""); 380 381 if (strcasecmp(nb_domain, info->domain_name)) 382 smb_browser_reconfig(); 383 384 return (NT_STATUS_SUCCESS); 385 } 386 387 static uint32_t 388 smbd_join_domain(smb_joininfo_t *info) 389 { 390 uint32_t status; 391 unsigned char passwd_hash[SMBAUTH_HASH_SZ]; 392 char dc[MAXHOSTNAMELEN]; 393 smb_domainex_t dxi; 394 smb_domain_t *di; 395 396 /* 397 * Ensure that any previous membership of this domain has 398 * been cleared from the environment before we start. This 399 * will ensure that we don't attempt a NETLOGON_SAMLOGON 400 * when attempting to find the PDC. 401 */ 402 403 (void) smb_config_setbool(SMB_CI_DOMAIN_MEMB, B_FALSE); 404 405 if (smb_auth_ntlm_hash(info->domain_passwd, passwd_hash) 406 != SMBAUTH_SUCCESS) { 407 syslog(LOG_ERR, "smbd: could not compute ntlm hash for '%s'", 408 info->domain_username); 409 return (NT_STATUS_INTERNAL_ERROR); 410 } 411 412 smb_ipc_set(info->domain_username, passwd_hash); 413 414 (void) smbd_get_kpasswd_srv(dc, sizeof (dc)); 415 /* info->domain_name could either be NetBIOS domain name or FQDN */ 416 if (smb_locate_dc(info->domain_name, dc, &dxi)) { 417 status = mlsvc_join(&dxi, info->domain_username, 418 info->domain_passwd); 419 420 if (status == NT_STATUS_SUCCESS) { 421 di = &dxi.d_primary; 422 smbd_set_secmode(SMB_SECMODE_DOMAIN); 423 smb_config_setdomaininfo(di->di_nbname, di->di_fqname, 424 di->di_sid, 425 di->di_u.di_dns.ddi_forest, 426 di->di_u.di_dns.ddi_guid); 427 smb_ipc_commit(); 428 return (status); 429 } 430 431 smb_ipc_rollback(); 432 syslog(LOG_ERR, "smbd: failed joining %s (%s)", 433 info->domain_name, xlate_nt_status(status)); 434 return (status); 435 } 436 437 smb_ipc_rollback(); 438 syslog(LOG_ERR, "smbd: failed locating domain controller for %s", 439 info->domain_name); 440 return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND); 441 } 442