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 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 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/libsmbrdr.h> 36 #include <smbsrv/libsmbns.h> 37 #include <smbsrv/libmlsvc.h> 38 #include <smbsrv/smbinfo.h> 39 #include <smbsrv/ntstatus.h> 40 #include "smbd.h" 41 42 43 /* 44 * This is a short-lived thread that triggers the initial DC discovery 45 * at startup. 46 */ 47 static pthread_t smb_locate_dc_thr; 48 49 static void *smbd_locate_dc_thread(void *); 50 static int smbd_get_kpasswd_srv(char *, size_t); 51 static uint32_t smbd_join_workgroup(smb_joininfo_t *); 52 static uint32_t smbd_join_domain(smb_joininfo_t *); 53 54 /* 55 * smbd_join 56 * 57 * Joins the specified domain/workgroup. 58 * 59 * If the security mode or domain name is being changed, 60 * the caller must restart the service. 61 */ 62 uint32_t 63 smbd_join(smb_joininfo_t *info) 64 { 65 uint32_t status; 66 67 dssetup_clear_domain_info(); 68 if (info->mode == SMB_SECMODE_WORKGRP) 69 status = smbd_join_workgroup(info); 70 else 71 status = smbd_join_domain(info); 72 73 return (status); 74 } 75 76 /* 77 * smbd_set_netlogon_cred 78 * 79 * If the system is joined to an AD domain via kclient, SMB daemon will need 80 * to establish the NETLOGON credential chain. 81 * 82 * Since the kclient has updated the machine password stored in SMF 83 * repository, the cached ipc_info must be updated accordingly by calling 84 * smbrdr_ipc_commit. 85 * 86 * Due to potential replication delays in a multiple DC environment, the 87 * NETLOGON rpc request must be sent to the DC, to which the KPASSWD request 88 * is sent. If the DC discovered by the SMB daemon is different than the 89 * kpasswd server, the current connection with the DC will be torn down 90 * and a DC discovery process will be triggered to locate the kpasswd 91 * server. 92 * 93 * If joining a new domain, the domain_name property must be set after a 94 * successful credential chain setup. 95 */ 96 boolean_t 97 smbd_set_netlogon_cred(void) 98 { 99 char kpasswd_srv[MAXHOSTNAMELEN]; 100 char kpasswd_domain[MAXHOSTNAMELEN]; 101 char sam_acct[SMB_SAMACCT_MAXLEN]; 102 char *ipc_usr, *dom; 103 boolean_t new_domain = B_FALSE; 104 smb_domain_t domain; 105 nt_domain_t *di; 106 107 if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN) 108 return (B_FALSE); 109 110 if (smb_match_netlogon_seqnum()) 111 return (B_FALSE); 112 113 (void) smb_config_getstr(SMB_CI_KPASSWD_SRV, kpasswd_srv, 114 sizeof (kpasswd_srv)); 115 116 if (*kpasswd_srv == '\0') 117 return (B_FALSE); 118 119 /* 120 * If the domain join initiated by smbadm join CLI is in 121 * progress, don't do anything. 122 */ 123 (void) smb_getsamaccount(sam_acct, sizeof (sam_acct)); 124 ipc_usr = smbrdr_ipc_get_user(); 125 if (utf8_strcasecmp(ipc_usr, sam_acct)) 126 return (B_FALSE); 127 128 di = &domain.d_info; 129 if (!smb_domain_getinfo(&domain)) 130 (void) smb_getfqdomainname(di->di_fqname, MAXHOSTNAMELEN); 131 132 (void) smb_config_getstr(SMB_CI_KPASSWD_DOMAIN, kpasswd_domain, 133 sizeof (kpasswd_domain)); 134 135 if (*kpasswd_domain != '\0' && 136 utf8_strcasecmp(kpasswd_domain, di->di_fqname)) { 137 dom = kpasswd_domain; 138 new_domain = B_TRUE; 139 } else { 140 dom = di->di_fqname; 141 } 142 143 /* 144 * DC discovery will be triggered if the domain info is not 145 * currently cached or the SMB daemon has previously discovered a DC 146 * that is different than the kpasswd server. 147 */ 148 if (new_domain || utf8_strcasecmp(domain.d_dc, kpasswd_srv) != 0) { 149 if (*domain.d_dc != '\0') 150 mlsvc_disconnect(domain.d_dc); 151 152 if (!smb_locate_dc(dom, kpasswd_srv, &domain)) { 153 if (!smb_locate_dc(di->di_fqname, "", &domain)) { 154 smbrdr_ipc_commit(); 155 return (B_FALSE); 156 } 157 } 158 } 159 160 smbrdr_ipc_commit(); 161 if (mlsvc_netlogon(domain.d_dc, di->di_nbname)) { 162 syslog(LOG_ERR, 163 "failed to establish NETLOGON credential chain"); 164 return (B_TRUE); 165 } else { 166 if (new_domain) { 167 smb_config_setdomaininfo(di->di_nbname, di->di_fqname, 168 di->di_sid, 169 di->di_u.di_dns.ddi_forest, 170 di->di_u.di_dns.ddi_guid); 171 (void) smb_config_setstr(SMB_CI_KPASSWD_DOMAIN, ""); 172 } 173 } 174 175 return (new_domain); 176 } 177 178 /* 179 * smbd_locate_dc_start() 180 * 181 * Initialization of the thread that triggers the initial DC discovery 182 * when SMB daemon starts up. 183 * Returns 0 on success, an error number if thread creation fails. 184 */ 185 int 186 smbd_locate_dc_start(void) 187 { 188 pthread_attr_t tattr; 189 int rc; 190 191 (void) pthread_attr_init(&tattr); 192 (void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED); 193 rc = pthread_create(&smb_locate_dc_thr, &tattr, smbd_locate_dc_thread, 194 NULL); 195 (void) pthread_attr_destroy(&tattr); 196 return (rc); 197 } 198 199 /* 200 * smbd_locate_dc_thread() 201 * 202 * If necessary, set up Netlogon credential chain and locate a 203 * domain controller in the given resource domain. 204 * 205 * The domain configuration will be updated upon a successful DC discovery. 206 */ 207 /*ARGSUSED*/ 208 static void * 209 smbd_locate_dc_thread(void *arg) 210 { 211 char domain[MAXHOSTNAMELEN]; 212 smb_domain_t new_domain; 213 nt_domain_t *di; 214 215 if (!smb_match_netlogon_seqnum()) { 216 (void) smbd_set_netlogon_cred(); 217 } else { 218 if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0) { 219 (void) smb_getdomainname(domain, MAXHOSTNAMELEN); 220 (void) utf8_strupr(domain); 221 } 222 223 if (smb_locate_dc(domain, "", &new_domain)) { 224 di = &new_domain.d_info; 225 smb_config_setdomaininfo(di->di_nbname, di->di_fqname, 226 di->di_sid, 227 di->di_u.di_dns.ddi_forest, 228 di->di_u.di_dns.ddi_guid); 229 } 230 } 231 232 return (NULL); 233 } 234 235 236 /* 237 * Retrieve the kpasswd server from krb5.conf. 238 * 239 * Initialization of the locate dc thread. 240 * Returns 0 on success, an error number if thread creation fails. 241 */ 242 static int 243 smbd_get_kpasswd_srv(char *srv, size_t len) 244 { 245 FILE *fp; 246 static char buf[512]; 247 char *p; 248 249 *srv = '\0'; 250 p = getenv("KRB5_CONFIG"); 251 if (p == NULL || *p == '\0') 252 p = "/etc/krb5/krb5.conf"; 253 254 if ((fp = fopen(p, "r")) == NULL) 255 return (-1); 256 257 while (fgets(buf, sizeof (buf), fp)) { 258 259 /* Weed out any comment text */ 260 (void) trim_whitespace(buf); 261 if (*buf == '#') 262 continue; 263 264 if ((p = strstr(buf, "kpasswd_server")) != NULL) { 265 if ((p = strchr(p, '=')) != NULL) { 266 (void) trim_whitespace(++p); 267 (void) strlcpy(srv, p, len); 268 } 269 break; 270 } 271 } 272 273 274 (void) fclose(fp); 275 return ((*srv == '\0') ? -1 : 0); 276 } 277 278 static uint32_t 279 smbd_join_workgroup(smb_joininfo_t *info) 280 { 281 char nb_domain[SMB_PI_MAX_DOMAIN]; 282 283 (void) smb_config_getstr(SMB_CI_DOMAIN_NAME, nb_domain, 284 sizeof (nb_domain)); 285 286 smbd_set_secmode(SMB_SECMODE_WORKGRP); 287 smb_config_setdomaininfo(info->domain_name, "", "", "", ""); 288 289 if (strcasecmp(nb_domain, info->domain_name)) 290 smb_browser_reconfig(); 291 292 return (NT_STATUS_SUCCESS); 293 } 294 295 static uint32_t 296 smbd_join_domain(smb_joininfo_t *info) 297 { 298 uint32_t status; 299 unsigned char passwd_hash[SMBAUTH_HASH_SZ]; 300 char dc[MAXHOSTNAMELEN]; 301 smb_domain_t domain_info; 302 nt_domain_t *di; 303 304 /* 305 * Ensure that any previous membership of this domain has 306 * been cleared from the environment before we start. This 307 * will ensure that we don't attempt a NETLOGON_SAMLOGON 308 * when attempting to find the PDC. 309 */ 310 311 (void) smb_config_setbool(SMB_CI_DOMAIN_MEMB, B_FALSE); 312 313 if (smb_auth_ntlm_hash(info->domain_passwd, passwd_hash) 314 != SMBAUTH_SUCCESS) { 315 syslog(LOG_ERR, "smbd: could not compute ntlm hash for '%s'", 316 info->domain_username); 317 return (NT_STATUS_INTERNAL_ERROR); 318 } 319 320 smbrdr_ipc_set(info->domain_username, passwd_hash); 321 322 (void) smbd_get_kpasswd_srv(dc, sizeof (dc)); 323 /* info->domain_name could either be NetBIOS domain name or FQDN */ 324 if (smb_locate_dc(info->domain_name, dc, &domain_info)) { 325 status = mlsvc_join(&domain_info, info->domain_username, 326 info->domain_passwd); 327 328 if (status == NT_STATUS_SUCCESS) { 329 di = &domain_info.d_info; 330 smbd_set_secmode(SMB_SECMODE_DOMAIN); 331 smb_config_setdomaininfo(di->di_nbname, di->di_fqname, 332 di->di_sid, 333 di->di_u.di_dns.ddi_forest, 334 di->di_u.di_dns.ddi_guid); 335 smbrdr_ipc_commit(); 336 return (status); 337 } 338 339 smbrdr_ipc_rollback(); 340 syslog(LOG_ERR, "smbd: failed joining %s (%s)", 341 info->domain_name, xlate_nt_status(status)); 342 return (status); 343 } 344 345 smbrdr_ipc_rollback(); 346 syslog(LOG_ERR, "smbd: failed locating domain controller for %s", 347 info->domain_name); 348 return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND); 349 } 350