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