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 /* 27 * This module handles the primary domain controller location protocol. 28 * The document claims to be version 1.15 of the browsing protocol. It also 29 * claims to specify the mailslot protocol. 30 * 31 * The NETLOGON protocol uses \MAILSLOT\NET mailslots. The protocol 32 * specification is incomplete, contains errors and is out-of-date but 33 * it does provide some useful background information. The document 34 * doesn't mention the NETLOGON_SAMLOGON version of the protocol. 35 */ 36 37 #include <stdlib.h> 38 #include <syslog.h> 39 #include <alloca.h> 40 #include <arpa/inet.h> 41 #include <resolv.h> 42 43 #include <smbsrv/mailslot.h> 44 #include <smbsrv/libsmbns.h> 45 #include <smbns_browser.h> 46 #include <smbns_netbios.h> 47 48 static void smb_netlogon_query(struct name_entry *server, char *mailbox, 49 char *domain); 50 51 static void smb_netlogon_samlogon(struct name_entry *, char *, 52 char *, smb_sid_t *); 53 54 static void smb_netlogon_send(struct name_entry *name, char *domain, 55 unsigned char *buffer, int count); 56 57 static void smb_netlogon_rdc_rsp(char *src_name, uint32_t src_ipaddr); 58 static int smb_better_dc(uint32_t cur_ip, uint32_t new_ip); 59 60 /* 61 * ntdomain_info 62 * Temporary. It should be removed once NBTD is integrated. 63 */ 64 extern smb_ntdomain_t ntdomain_info; 65 extern mutex_t ntdomain_mtx; 66 extern cond_t ntdomain_cv; 67 68 /* 69 * smb_netlogon_request 70 * 71 * This is the entry point locating the resource domain PDC. A netlogon 72 * request is sent using the specified protocol on the specified network. 73 * Note that we need to know the domain SID in order to use the samlogon 74 * format. 75 * 76 * Netlogon responses are received asynchronously and eventually handled 77 * in smb_netlogon_receive. 78 */ 79 void 80 smb_netlogon_request(struct name_entry *server, char *domain) 81 { 82 smb_domain_t di; 83 smb_sid_t *sid = NULL; 84 int protocol = NETLOGON_PROTO_NETLOGON; 85 86 if (domain == NULL || *domain == '\0') 87 return; 88 89 (void) mutex_lock(&ntdomain_mtx); 90 (void) strlcpy(ntdomain_info.n_domain, domain, 91 sizeof (ntdomain_info.n_domain)); 92 (void) mutex_unlock(&ntdomain_mtx); 93 94 smb_config_getdomaininfo(di.di_nbname, NULL, di.di_sid, NULL, NULL); 95 if (utf8_strcasecmp(di.di_nbname, domain) == 0) { 96 if ((sid = smb_sid_fromstr(di.di_sid)) != NULL) 97 protocol = NETLOGON_PROTO_SAMLOGON; 98 } 99 100 if (protocol == NETLOGON_PROTO_SAMLOGON) 101 smb_netlogon_samlogon(server, MAILSLOT_NETLOGON_SAMLOGON_RDC, 102 domain, sid); 103 else 104 smb_netlogon_query(server, MAILSLOT_NETLOGON_RDC, domain); 105 106 smb_sid_free(sid); 107 } 108 109 /* 110 * smb_netlogon_receive 111 * 112 * This is where we handle all incoming NetLogon messages. Currently, we 113 * ignore requests from anyone else. We are only interested in responses 114 * to our own requests. The NetLogonResponse provides the name of the PDC. 115 * If we don't already have a controller name, we use the name provided 116 * in the message. Otherwise we use the name already in the environment. 117 */ 118 void 119 smb_netlogon_receive(struct datagram *datagram, 120 char *mailbox, 121 unsigned char *data, 122 int datalen) 123 { 124 struct netlogon_opt { 125 char *mailslot; 126 void (*handler)(); 127 } netlogon_opt[] = { 128 { MAILSLOT_NETLOGON_RDC, smb_netlogon_rdc_rsp }, 129 { MAILSLOT_NETLOGON_SAMLOGON_RDC, smb_netlogon_rdc_rsp }, 130 }; 131 132 smb_msgbuf_t mb; 133 unsigned short opcode; 134 char src_name[SMB_PI_MAX_HOST]; 135 mts_wchar_t unicode_src_name[SMB_PI_MAX_HOST]; 136 unsigned int cpid = oem_get_smb_cpid(); 137 uint32_t src_ipaddr; 138 char *junk; 139 char *primary; 140 char *domain; 141 int i; 142 char ipstr[16]; 143 int rc; 144 145 src_ipaddr = datagram->src.addr_list.sin.sin_addr.s_addr; 146 147 /* 148 * The datagram->src.name is in oem codepage format. 149 * Therefore, we need to convert it to unicode and 150 * store it in multi-bytes format. 151 */ 152 (void) oemstounicodes(unicode_src_name, (char *)datagram->src.name, 153 SMB_PI_MAX_HOST, cpid); 154 (void) mts_wcstombs(src_name, unicode_src_name, SMB_PI_MAX_HOST); 155 156 (void) trim_whitespace(src_name); 157 158 (void) inet_ntop(AF_INET, (const void *)(&src_ipaddr), ipstr, 159 sizeof (ipstr)); 160 syslog(LOG_DEBUG, "NetLogonReceive: src=%s [%s], mbx=%s", 161 src_name, ipstr, mailbox); 162 163 smb_msgbuf_init(&mb, data, datalen, 0); 164 165 if (smb_msgbuf_decode(&mb, "w", &opcode) < 0) { 166 syslog(LOG_ERR, "NetLogonReceive: decode error"); 167 smb_msgbuf_term(&mb); 168 return; 169 } 170 171 switch (opcode) { 172 case LOGON_PRIMARY_RESPONSE: 173 /* 174 * Message contains: 175 * PDC name (MBS), PDC name (Unicode), Domain name (unicode) 176 */ 177 rc = smb_msgbuf_decode(&mb, "sUU", &junk, &primary, &domain); 178 if (rc < 0) { 179 syslog(LOG_ERR, 180 "NetLogonResponse: opcode %d decode error", 181 opcode); 182 smb_msgbuf_term(&mb); 183 return; 184 } 185 break; 186 187 case LOGON_SAM_LOGON_RESPONSE: 188 case LOGON_SAM_USER_UNKNOWN: 189 /* 190 * Message contains: 191 * PDC name, User name, Domain name (all unicode) 192 */ 193 rc = smb_msgbuf_decode(&mb, "UUU", &primary, &junk, &domain); 194 if (rc < 0) { 195 syslog(LOG_ERR, 196 "NetLogonResponse: opcode %d decode error", 197 opcode); 198 smb_msgbuf_term(&mb); 199 return; 200 } 201 202 /* 203 * skip past the "\\" prefix 204 */ 205 primary += strspn(primary, "\\"); 206 break; 207 208 default: 209 /* 210 * We don't respond to PDC discovery requests. 211 */ 212 syslog(LOG_DEBUG, "NetLogonReceive: opcode 0x%04x", opcode); 213 smb_msgbuf_term(&mb); 214 return; 215 } 216 217 if (domain == NULL || primary == NULL) { 218 syslog(LOG_ERR, "NetLogonResponse: malformed packet"); 219 smb_msgbuf_term(&mb); 220 return; 221 } 222 223 syslog(LOG_DEBUG, "DC Offer Domain=%s PDC=%s From=%s", 224 domain, primary, src_name); 225 226 (void) mutex_lock(&ntdomain_mtx); 227 if (strcasecmp(domain, ntdomain_info.n_domain)) { 228 syslog(LOG_DEBUG, "NetLogonResponse: other domain " 229 "%s, requested %s", domain, ntdomain_info.n_domain); 230 smb_msgbuf_term(&mb); 231 (void) mutex_unlock(&ntdomain_mtx); 232 return; 233 } 234 (void) mutex_unlock(&ntdomain_mtx); 235 236 for (i = 0; i < sizeof (netlogon_opt)/sizeof (netlogon_opt[0]); ++i) { 237 if (strcasecmp(netlogon_opt[i].mailslot, mailbox) == 0) { 238 syslog(LOG_DEBUG, "NetLogonReceive: %s", mailbox); 239 (*netlogon_opt[i].handler)(primary, src_ipaddr); 240 smb_msgbuf_term(&mb); 241 return; 242 } 243 } 244 245 syslog(LOG_DEBUG, "NetLogonReceive[%s]: unknown mailslot", mailbox); 246 smb_msgbuf_term(&mb); 247 } 248 249 250 251 /* 252 * smb_netlogon_query 253 * 254 * Build and send a LOGON_PRIMARY_QUERY to the MAILSLOT_NETLOGON. At some 255 * point we should receive a LOGON_PRIMARY_RESPONSE in the mailslot we 256 * specify in the request. 257 * 258 * struct NETLOGON_QUERY { 259 * unsigned short Opcode; # LOGON_PRIMARY_QUERY 260 * char ComputerName[]; # ASCII hostname. The response 261 * # is sent to <ComputerName>(00). 262 * char MailslotName[]; # MAILSLOT_NETLOGON 263 * char Pad[]; # Pad to short 264 * wchar_t ComputerName[] # UNICODE hostname 265 * DWORD NT_Version; # 0x00000001 266 * WORD LmNTToken; # 0xffff 267 * WORD Lm20Token; # 0xffff 268 * }; 269 */ 270 static void 271 smb_netlogon_query(struct name_entry *server, 272 char *mailbox, 273 char *domain) 274 { 275 smb_msgbuf_t mb; 276 int offset, announce_len, data_length, name_lengths; 277 unsigned char buffer[MAX_DATAGRAM_LENGTH]; 278 char hostname[NETBIOS_NAME_SZ]; 279 280 if (smb_getnetbiosname(hostname, sizeof (hostname)) != 0) 281 return; 282 283 name_lengths = strlen(mailbox)+1+strlen(hostname)+1; 284 285 /* 286 * The (name_lengths & 1) part is to word align the name_lengths 287 * before the wc equiv strlen and the "+ 2" is to cover the two 288 * zero bytes that terminate the wchar string. 289 */ 290 data_length = sizeof (short) + name_lengths + (name_lengths & 1) + 291 mts_wcequiv_strlen(hostname) + 2 + sizeof (long) + sizeof (short) + 292 sizeof (short); 293 294 offset = smb_browser_load_transact_header(buffer, 295 sizeof (buffer), data_length, ONE_WAY_TRANSACTION, 296 MAILSLOT_NETLOGON); 297 298 if (offset < 0) 299 return; 300 301 smb_msgbuf_init(&mb, buffer + offset, sizeof (buffer) - offset, 0); 302 303 announce_len = smb_msgbuf_encode(&mb, "wssUlww", 304 (short)LOGON_PRIMARY_QUERY, 305 hostname, 306 mailbox, 307 hostname, 308 0x1, 309 0xffff, 310 0xffff); 311 312 if (announce_len <= 0) { 313 smb_msgbuf_term(&mb); 314 syslog(LOG_ERR, "NetLogonQuery: encode error"); 315 return; 316 } 317 318 smb_netlogon_send(server, domain, buffer, offset + announce_len); 319 smb_msgbuf_term(&mb); 320 } 321 322 323 /* 324 * smb_netlogon_samlogon 325 * 326 * The SamLogon version of the NetLogon request uses the workstation trust 327 * account and, I think, may be a prerequisite to the challenge/response 328 * netr authentication. The trust account username is the hostname with a 329 * $ appended. The mailslot for this request is MAILSLOT_NTLOGON. At some 330 * we should receive a LOGON_SAM_LOGON_RESPONSE in the mailslot we 331 * specify in the request. 332 * 333 * struct NETLOGON_SAM_LOGON { 334 * unsigned short Opcode; # LOGON_SAM_LOGON_REQUEST 335 * unsigned short RequestCount; # 0 336 * wchar_t UnicodeComputerName; # hostname 337 * wchar_t UnicodeUserName; # hostname$ 338 * char *MailslotName; # response mailslot 339 * DWORD AllowableAccountControlBits; # 0x80 = WorkstationTrustAccount 340 * DWORD DomainSidSize; # domain sid length in bytes 341 * BYTE *DomainSid; # domain sid 342 * uint32_t NT_Version; # 0x00000001 343 * unsigned short LmNTToken; # 0xffff 344 * unsigned short Lm20Token; # 0xffff 345 * }; 346 */ 347 static void 348 smb_netlogon_samlogon(struct name_entry *server, 349 char *mailbox, 350 char *domain, 351 smb_sid_t *domain_sid) 352 { 353 smb_msgbuf_t mb; 354 unsigned domain_sid_len; 355 char *username; 356 unsigned char buffer[MAX_DATAGRAM_LENGTH]; 357 int offset; 358 int announce_len; 359 int data_length; 360 int name_length; 361 char hostname[NETBIOS_NAME_SZ]; 362 363 syslog(LOG_DEBUG, "NetLogonSamLogonReq: %s", domain); 364 365 if (smb_getnetbiosname(hostname, sizeof (hostname)) != 0) 366 return; 367 368 /* 369 * The username will be the trust account name on the PDC. 370 */ 371 name_length = strlen(hostname) + 2; 372 username = alloca(name_length); 373 (void) snprintf(username, name_length, "%s$", hostname); 374 375 domain_sid_len = smb_sid_len(domain_sid); 376 /* 377 * Add 2 to wide-char equivalent strlen to cover the 378 * two zero bytes that terminate the wchar string. 379 */ 380 name_length = strlen(mailbox)+1; 381 382 data_length = sizeof (short) 383 + sizeof (short) 384 + mts_wcequiv_strlen(hostname) + 2 385 + mts_wcequiv_strlen(username) + 2 386 + name_length 387 + sizeof (long) 388 + sizeof (long) 389 + domain_sid_len + 3 /* padding */ 390 + sizeof (long) 391 + sizeof (short) 392 + sizeof (short); 393 394 offset = smb_browser_load_transact_header(buffer, 395 sizeof (buffer), data_length, ONE_WAY_TRANSACTION, 396 MAILSLOT_NTLOGON); 397 398 if (offset < 0) { 399 syslog(LOG_ERR, "NetLogonSamLogonReq: header error"); 400 return; 401 } 402 403 /* 404 * The domain SID is padded with 3 leading zeros. 405 */ 406 smb_msgbuf_init(&mb, buffer + offset, sizeof (buffer) - offset, 0); 407 announce_len = smb_msgbuf_encode(&mb, "wwUUsll3.#clww", 408 (short)LOGON_SAM_LOGON_REQUEST, 409 0, /* RequestCount */ 410 hostname, /* UnicodeComputerName */ 411 username, /* UnicodeUserName */ 412 mailbox, /* MailslotName */ 413 0x00000080, /* AllowableAccountControlBits */ 414 domain_sid_len, /* DomainSidSize */ 415 domain_sid_len, domain_sid, /* DomainSid */ 416 0x00000001, /* NT_Version */ 417 0xffff, /* LmNTToken */ 418 0xffff); /* Lm20Token */ 419 420 if (announce_len <= 0) { 421 syslog(LOG_ERR, "NetLogonSamLogonReq: encode error"); 422 smb_msgbuf_term(&mb); 423 return; 424 } 425 426 smb_netlogon_send(server, domain, buffer, offset + announce_len); 427 smb_msgbuf_term(&mb); 428 } 429 430 431 /* 432 * Send a query for each version of the protocol. 433 */ 434 static void 435 smb_netlogon_send(struct name_entry *name, 436 char *domain, 437 unsigned char *buffer, 438 int count) 439 { 440 static char suffix[] = { 0x1B, 0x1C }; 441 struct name_entry dname; 442 struct name_entry *dest; 443 struct name_entry *dest_dup; 444 int i; 445 446 for (i = 0; i < sizeof (suffix)/sizeof (suffix[0]); i++) { 447 smb_init_name_struct((unsigned char *)domain, suffix[i], 448 0, 0, 0, 0, 0, &dname); 449 450 syslog(LOG_DEBUG, "SmbNetlogonSend"); 451 smb_netbios_name_logf(&dname); 452 if ((dest = smb_name_find_name(&dname)) != 0) { 453 dest_dup = smb_netbios_name_dup(dest, 1); 454 smb_name_unlock_name(dest); 455 if (dest_dup) { 456 (void) smb_netbios_datagram_send(name, 457 dest_dup, buffer, count); 458 free(dest_dup); 459 } 460 } else { 461 syslog(LOG_DEBUG, 462 "SmbNetlogonSend: could not find %s<0x%X>", 463 domain, suffix[i]); 464 } 465 } 466 } 467 468 /* 469 * smb_netlogon_rdc_rsp 470 * 471 * This is where we process netlogon responses for the resource domain. 472 * The src_name is the real name of the remote machine. 473 */ 474 static void 475 smb_netlogon_rdc_rsp(char *src_name, uint32_t src_ipaddr) 476 { 477 static int initialized = 0; 478 uint32_t ipaddr; 479 uint32_t prefer_ipaddr; 480 char ipstr[INET_ADDRSTRLEN]; 481 char srcip[INET_ADDRSTRLEN]; 482 int rc; 483 484 (void) inet_ntop(AF_INET, &src_ipaddr, srcip, INET_ADDRSTRLEN); 485 486 rc = smb_config_getstr(SMB_CI_DOMAIN_SRV, ipstr, INET_ADDRSTRLEN); 487 if (rc == SMBD_SMF_OK) { 488 rc = inet_pton(AF_INET, ipstr, &prefer_ipaddr); 489 if (rc == 0) 490 prefer_ipaddr = 0; 491 492 if (!initialized) { 493 syslog(LOG_DEBUG, "SMB DC Preference: %s", ipstr); 494 initialized = 1; 495 } 496 } 497 498 (void) mutex_lock(&ntdomain_mtx); 499 syslog(LOG_DEBUG, "DC Offer [%s]: %s [%s]", 500 ntdomain_info.n_domain, src_name, srcip); 501 502 if (ntdomain_info.n_ipaddr != 0) { 503 if (prefer_ipaddr != 0 && 504 prefer_ipaddr == ntdomain_info.n_ipaddr) { 505 syslog(LOG_DEBUG, "DC for %s: %s [%s]", 506 ntdomain_info.n_domain, src_name, srcip); 507 (void) mutex_unlock(&ntdomain_mtx); 508 return; 509 } 510 511 ipaddr = ntdomain_info.n_ipaddr; 512 } else 513 ipaddr = 0; 514 515 if (smb_better_dc(ipaddr, src_ipaddr) || 516 (prefer_ipaddr != 0 && prefer_ipaddr == src_ipaddr)) { 517 /* set nbtd cache */ 518 (void) strlcpy(ntdomain_info.n_name, src_name, 519 SMB_PI_MAX_DOMAIN); 520 ntdomain_info.n_ipaddr = src_ipaddr; 521 (void) cond_broadcast(&ntdomain_cv); 522 syslog(LOG_DEBUG, "DC discovered for %s: %s [%s]", 523 ntdomain_info.n_domain, src_name, srcip); 524 } 525 (void) mutex_unlock(&ntdomain_mtx); 526 } 527 528 static int 529 smb_better_dc(uint32_t cur_ip, uint32_t new_ip) 530 { 531 smb_inaddr_t ipaddr; 532 533 /* 534 * If we don't have any current DC, 535 * then use the new one of course. 536 */ 537 538 if (cur_ip == 0) 539 return (1); 540 /* 541 * see if there is a DC in the 542 * same subnet 543 */ 544 545 ipaddr.a_family = AF_INET; 546 ipaddr.a_ipv4 = cur_ip; 547 if (smb_nic_is_same_subnet(&ipaddr)) 548 return (0); 549 550 ipaddr.a_family = AF_INET; 551 ipaddr.a_ipv4 = new_ip; 552 if (smb_nic_is_same_subnet(&ipaddr)) 553 return (1); 554 /* 555 * Otherwise, just keep the old one. 556 */ 557 return (0); 558 } 559