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