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