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