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